From 0df0c5da430f7e8504afbdb935b268c5fd19bca9 Mon Sep 17 00:00:00 2001 From: ti_mo Date: Fri, 22 May 2026 19:16:08 +0200 Subject: [PATCH] Task 2 should be finished --- lab_3/task1.asv | 146 +++++++++++++++++++++++++++++++ lab_3/task1.m | 84 +++++++++++++++--- lab_3/task2.asv | 227 ++++++++++++++++++++++++++++++++++++++++++++++-- lab_3/task2.m | 175 +++++++++++++++++++++++++++++++++---- 4 files changed, 591 insertions(+), 41 deletions(-) create mode 100644 lab_3/task1.asv diff --git a/lab_3/task1.asv b/lab_3/task1.asv new file mode 100644 index 0000000..872dc2b --- /dev/null +++ b/lab_3/task1.asv @@ -0,0 +1,146 @@ +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 -> 20e6 S/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 +[S1,F1,T1] = spectrogram(trace1.iq, window, overlap, fft_precision, sample_rate, "centered"); +[S2,F2,T2] = spectrogram(trace2.iq, window, overlap, fft_precision, sample_rate, "centered"); +[S3,F3,T3] = spectrogram(trace3.iq, window, overlap, fft_precision, sample_rate, "centered"); +[S4,F4,T4] = spectrogram(trace4.iq, window, overlap, fft_precision, sample_rate, "centered"); + +mag1 = abs(S1).^2; +mag2 = abs(S2).^2; +mag3 = abs(S3).^2; +mag4 = abs(S4).^2; + +% begin calibrated color scale +P1_dB = 10 * log10(mag1); +P2_dB = 10 * log10(mag2); +P3_dB = 10 * log10(mag3); +P4_dB = 10 * log10(mag4); + +all_P_dB = [P1_dB(:); P2_dB(:); P3_dB(:); P4_dB(:)]; + +p_dB_min = min(all_P_dB); +p_dB_max = max(all_P_dB); +% end power calibrated color scale + +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. +% taking the 10th percentile on the linear values or the logarithmic ones +% should not make any difference + +noise_floor1_linear = prctile(mag1(:), 10); +noise_floor2_linear = prctile(mag2(:), 10); +noise_floor3_linear = prctile(mag3(:), 10); +noise_floor4_linear = prctile(mag4(:), 10); + +noise_floor1 = 10 * log10(noise_floor1_linear); +noise_floor2 = prctile(P2_dB(:), 10); +noise_floor3 = prctile(P3_dB(:), 10); +noise_floor4 = prctile(P4_dB(:), 10); + +% this is false -> linear avg on logarithmic values +% noise_floor_avg = sum([noise_floor1 noise_floor2, noise_floor3, noise_floor4]) / 4; + +noise_floor_avg = 10 * log10(abs(sum([prctile(mag1(:), 10) prctile(mag2(:), 10) prctile(mag3(:), 10) prctile(mag4(:), 10)]) / 4)); + +% 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); + +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("--- 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("-- 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) +end \ No newline at end of file diff --git a/lab_3/task1.m b/lab_3/task1.m index 7898a3b..2b96f7a 100644 --- a/lab_3/task1.m +++ b/lab_3/task1.m @@ -22,15 +22,19 @@ duration4 = length(trace4.iq) / sample_rate; [S3,F3,T3] = spectrogram(trace3.iq, window, overlap, fft_precision, sample_rate, "centered"); [S4,F4,T4] = spectrogram(trace4.iq, window, overlap, fft_precision, sample_rate, "centered"); +mag1 = abs(S1).^2; +mag2 = abs(S2).^2; +mag3 = abs(S3).^2; +mag4 = abs(S4).^2; + % begin calibrated color scale -P1_dB = 20 * log10(abs(S1).^2); -P2_dB = 20 * log10(abs(S2).^2); -P3_dB = 20 * log10(abs(S3).^2); -P4_dB = 20 * log10(abs(S4).^2); +P1_dB = 10 * log10(mag1); +P2_dB = 10 * log10(mag2); +P3_dB = 10 * log10(mag3); +P4_dB = 10 * log10(mag4); all_P_dB = [P1_dB(:); P2_dB(:); P3_dB(:); P4_dB(:)]; -% -50 to +50 so its like the example fig p_dB_min = min(all_P_dB); p_dB_max = max(all_P_dB); % end power calibrated color scale @@ -46,7 +50,7 @@ if ~calculations_only cb = colorbar; ylabel(cb, "Power [dB]"); clim([p_dB_min p_dB_max]); - xlim([0 0.05]); + % xlim([0 0.05]); figure(2); imagesc(T2, F2, P2_dB); @@ -57,7 +61,7 @@ if ~calculations_only cb = colorbar; ylabel(cb, "Power [dB]"); clim([p_dB_min p_dB_max]); - xlim([0 0.05]); + % xlim([0 0.05]); figure(3); imagesc(T3, F3, P3_dB); @@ -68,7 +72,7 @@ if ~calculations_only cb = colorbar; ylabel(cb, "Power [dB]"); clim([p_dB_min p_dB_max]); - xlim([0 0.05]); + % xlim([0 0.05]); figure(4); imagesc(T4, F4, P4_dB); @@ -79,22 +83,70 @@ if ~calculations_only 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 + % xlim([0 0.05]); % reduce view so its like the example fig end +%{ % 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); +% taking the 10th percentile on the linear values or the logarithmic ones +% should not make any difference -noise_floor_avg = sum([noise_floor1 noise_floor2, noise_floor3, noise_floor4]) / 4; +noise_floor1_linear = prctile(mag1(:), 10); +noise_floor2_linear = prctile(mag2(:), 10); +noise_floor3_linear = prctile(mag3(:), 10); +noise_floor4_linear = prctile(mag4(:), 10); + +noise_floor1 = linearTodB(noise_floor1_linear); +noise_floor2 = linearTodB(noise_floor2_linear); +noise_floor3 = linearTodB(noise_floor3_linear); +noise_floor4 = linearTodB(noise_floor4_linear); + +% this is false -> linear avg on logarithmic values +% noise_floor_avg = sum([noise_floor1 noise_floor2, noise_floor3, noise_floor4]) / 4; + +noise_floor_avg = linearTodB(mean([noise_floor1_linear noise_floor2_linear noise_floor3_linear noise_floor4_linear])); % 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; +%} + +% 3. +% Sum all frequency bins per time slot to obtain total channel power. +channel_power1_linear = sum(mag1, 1); +channel_power2_linear = sum(mag2, 1); +channel_power3_linear = sum(mag3, 1); +channel_power4_linear = sum(mag4, 1); + +channel_power1_dB = linearTodB(channel_power1_linear); +channel_power2_dB = linearTodB(channel_power2_linear); +channel_power3_dB = linearTodB(channel_power3_linear); +channel_power4_dB = linearTodB(channel_power4_linear); + +noise_floor1_linear = prctile(channel_power1_linear, 10); +noise_floor2_linear = prctile(channel_power2_linear, 10); +noise_floor3_linear = prctile(channel_power3_linear, 10); +noise_floor4_linear = prctile(channel_power4_linear, 10); + +noise_floor1 = linearTodB(noise_floor1_linear); +noise_floor2 = linearTodB(noise_floor2_linear); +noise_floor3 = linearTodB(noise_floor3_linear); +noise_floor4 = linearTodB(noise_floor4_linear); + +noise_floor_avg = linearTodB(mean([ + noise_floor1_linear + noise_floor2_linear + noise_floor3_linear + noise_floor4_linear + ])); + +% 4. +occupancy1 = sum(channel_power1_dB > noise_floor1 + 10) / numel(channel_power1_dB) * 100; +occupancy2 = sum(channel_power2_dB > noise_floor2 + 10) / numel(channel_power2_dB) * 100; +occupancy3 = sum(channel_power3_dB > noise_floor3 + 10) / numel(channel_power3_dB) * 100; +occupancy4 = sum(channel_power4_dB > noise_floor4 + 10) / numel(channel_power4_dB) * 100; occupancies = [occupancy1 occupancy2 occupancy3 occupancy4]; channels = [1 5 9 13]; @@ -128,4 +180,8 @@ if ~calculations_only disp("Channel 13 occupancy: " + occupancy4 + "%" + newline) disp("Busiest channel: " + busiest_channel) +end + +function val = linearTodB(toConvert) + val = 10 * log10(toConvert); end \ No newline at end of file diff --git a/lab_3/task2.asv b/lab_3/task2.asv index 443f5ed..4b07ef7 100644 --- a/lab_3/task2.asv +++ b/lab_3/task2.asv @@ -1,18 +1,81 @@ -channel_bandwidth = "CBW20"; % 20MHz +addpath("from_matlab_example"); rxFrame1 = trace1.iq(:); rxFrame2 = trace2.iq(:); rxFrame3 = trace3.iq(:); rxFrame4 = trace4.iq(:); -function beaconFrames = extractBeaconFrames(rxFrame) - beaconFrames = []; - searchOffset = 0; +beaconFrames1 = extractBeaconFrames(rxFrame1); +beaconFrames2 = extractBeaconFrames(rxFrame2); +beaconFrames3 = extractBeaconFrames(rxFrame3); +beaconFrames4 = extractBeaconFrames(rxFrame4); +% 1. +disp("Beacon Frame 1") +printBeaconTable(beaconFrames1); +disp("Beacon Frame 2") +printBeaconTable(beaconFrames2); +disp("Beacon Frame 3") +printBeaconTable(beaconFrames3); +disp("Beacon Frame 4") +printBeaconTable(beaconFrames4); + +% 2. +%{ +Address 1: used to announce the destination station, in a beacon frame its + FF:FF:FF:FF:FF:FF (meaning broadcast to all stations) +Address 2: used to remark the source device address of the frame, + the ap mac who sent the beacon is inserted +Address 3: used for the bssid of the device sending the beacon, means + usually address 2 equals address 3, when no virtual ap is used +Address 4: this field does not exists, after the seq ctrl the frame body + follows containing beacon data instead of an address, like + timestamp, beacon interval, capability info, ssid, potentially + the channel number, see the extracts from 2.1 +%} + +% 3. +%{ +Downlink: +Address 1: MAC of the destination station +Address 2: MAC of the AP which further routes the frame +Address 3: MAC of the initial sender which has started the process of + the frame to AP's +Address 4: not used +Uplink: +Address 1: MAC of the AP the WiFi frame should route through + (the connected AP) +Address 2: MAC of the sending station +Address 3: used to store the final destination MAC address + +Address 4: not used + +4 Address case: +Address 4: is only used in wireless bridge solutions, environments where + multiple AP's connect different LAN's to one big one +%} + +function beaconFrames = extractBeaconFrames(rxFrame) + beaconFrames = struct( ... + "SSID", {}, ... + "BSSID", {}, ... + "Offset", {}, ... + "MAC_Config", {}, ... + "Bits", {}, ... + "SNR_dB", {} ... + ); + + 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 @@ -20,10 +83,158 @@ function beaconFrames = extractBeaconFrames(rxFrame) [cfgMAC, ~, decodeStatus] = wlanMPDUDecode(bitsData, SuppressWarnings=true); if ~decodeStatus && matches(cfgMAC.FrameType, "Beacon") - disp("Beacon at " + searchOffset); - beaconFrames(end + 1) = cfgMAC; + 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 -beaconFrames1 = extractBeaconFrames(rxFrame1); \ No newline at end of file +function printBeaconTable(beaconFrames) + beaconCount = numel(beaconFrames); + + tableFormat = "%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-80s\n"; + + if(beaconCount < 1) + disp("No valid beacons could be extracted") + return; + end + + fprintf("\n"); + fprintf(tableFormat, ... + "SSID", "BSSID", "Interval", "B. ch.", "Op. ch.", "SNR", "Vendor", "Std.", "Rates"); + + for i = 1 : beaconCount + mgmt = beaconFrames(i).MAC_Config.ManagementConfig; + + ssid = string(beaconFrames(i).SSID); + bssid = toMACAddress(beaconFrames(i).BSSID); + interval = string(mgmt.BeaconInterval); + snr = sprintf("%.1f", beaconFrames(i).SNR_dB); + + [bch, opch] = extractChannels(mgmt); + vendor = lookupVendor(beaconFrames(i).BSSID); + rates = getRates(mgmt); + std = getWifiStandard(mgmt); + + fprintf(tableFormat, ... + ssid, bssid, interval, bch, opch, snr, vendor, std, rates); + end + + fprintf("\n"); +end + +function mac = toMACAddress(mac) + mac = upper(string(mac)); + + if strlength(mac) ~= 12 + return; + end + + macChars = char(mac); + + mac = string(sprintf("%s:%s:%s:%s:%s:%s", ... + macChars(1:2), ... + macChars(3:4), ... + macChars(5:6), ... + macChars(7:8), ... + macChars(9:10), ... + macChars(11:12))); +end + +function rates = getRates(mgmt) + basicRates = string(mgmt.BasicRates); + additionalRates = string(mgmt.AdditionalRates); + + if isempty(basicRates) && isempty(additionalRates) + rates = "-"; + return; + end + + basicRates = "[" + strjoin(basicRates, ", ") + "]"; + rates = strjoin([basicRates, additionalRates], ", "); +end + +function vendor = lookupVendor(bssid) + bssid = upper(string(bssid)); + + if strlength(bssid) < 6 + vendor = "-"; + return; + end + + oui = extractBefore(bssid, 7); + + switch oui + case { "C884A1", "5CE176" } + vendor = "Cisco"; + case { "6466B3", "647002" } + vendor = "TP-Link"; + otherwise + vendor = "Unknown"; + end +end + +function std = getWifiStandard(mgmt) + rates = [string(mgmt.BasicRates), string(mgmt.AdditionalRates)]; + + if any(contains(rates, ["6 Mbps", "9 Mbps", "12 Mbps", "18 Mbps", "24 Mbps", "36 Mbps", "48 Mbps", "54 Mbps"])) + std = "802.11g"; + elseif any(contains(rates, ["1 Mbps", "2 Mbps", "5.5 Mbps", "11 Mbps"])) + std = "802.11b"; + else + std = "Unknown"; + end +end + +function [b_ch, op_ch] = extractChannels(mgmt) + b_ch = "-"; + op_ch = "-"; + + ies = mgmt.InformationElements; + + if(isempty(ies)) + return; + end + + for i = 1 : size(ies, 1) + id = ies{i, 1}; + value = ies{i, 2}; + + if isempty(id) || isempty(value) + continue; + end + + switch id(1) + case 3 + b_ch = string(double(value(1))); + case 61 + op_ch = string(double(value(1))); + end + + if ~strcmp(b_ch, "-") && ~strcmp(op_ch, "-") + return; + end + end +end \ No newline at end of file diff --git a/lab_3/task2.m b/lab_3/task2.m index bf79156..cda12a0 100644 --- a/lab_3/task2.m +++ b/lab_3/task2.m @@ -10,17 +10,52 @@ beaconFrames2 = extractBeaconFrames(rxFrame2); beaconFrames3 = extractBeaconFrames(rxFrame3); beaconFrames4 = extractBeaconFrames(rxFrame4); -cfgMAC = beaconFrames1(1).MAC_Config; -mgmt = cfgMAC.ManagementConfig; - -disp(mgmt) -properties(mgmt) - +% 1. +disp("Beacon Frame 1") printBeaconTable(beaconFrames1); +disp("Beacon Frame 2") printBeaconTable(beaconFrames2); +disp("Beacon Frame 3") printBeaconTable(beaconFrames3); +disp("Beacon Frame 4") printBeaconTable(beaconFrames4); +% 2. +%{ +Address 1: used to announce the destination station, in a beacon frame its + FF:FF:FF:FF:FF:FF (meaning broadcast to all stations) +Address 2: used to remark the source device address of the frame, + the ap mac who sent the beacon is inserted +Address 3: used for the bssid of the device sending the beacon, means + usually address 2 equals address 3, when no virtual ap is used +Address 4: this field does not exists, after the seq ctrl the frame body + follows containing beacon data instead of an address, like + timestamp, beacon interval, capability info, ssid, potentially + the channel number, see the extracts from 2.1 +%} + +% 3. +%{ +Downlink: AP -> Station +Address 1: MAC of the destination station +Address 2: MAC of the AP which transmits the frame +Address 3: MAC of the initial sender which has started the frame +Address 4: not used + +Uplink: Station -> AP +Address 1: MAC of the AP that receives and potentially forwards the frame +Address 2: MAC of the sending station +Address 3: MAC of the final destination station +Address 4: not used + +4 Address case: wireless bridge: +Address 1: MAC of the AP that should receive / transmit through the frame, + for example AP, bridge node, repeater or mesh node +Address 2: MAC of the transmitter (into the other network) +Address 3: MAC of the final destination +Address 4: MAC of the initial sender +%} + function beaconFrames = extractBeaconFrames(rxFrame) beaconFrames = struct( ... "SSID", {}, ... @@ -31,8 +66,6 @@ function beaconFrames = extractBeaconFrames(rxFrame) "SNR_dB", {} ... ); - channel_bandwidth = "CBW20"; % 20MHz - searchOffset = 0; index = 1; @@ -80,25 +113,129 @@ function beaconFrames = extractBeaconFrames(rxFrame) end function printBeaconTable(beaconFrames) + beaconCount = numel(beaconFrames); + + tableFormat = "%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-80s\n"; + + if(beaconCount < 1) + disp("No valid beacons could be extracted") + return; + end + fprintf("\n"); - fprintf("%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-20s\n", ... + fprintf(tableFormat, ... "SSID", "BSSID", "Interval", "B. ch.", "Op. ch.", "SNR", "Vendor", "Std.", "Rates"); - - for i = 1:numel(beaconFrames) + + for i = 1 : beaconCount + mgmt = beaconFrames(i).MAC_Config.ManagementConfig; + ssid = string(beaconFrames(i).SSID); - bssid = string(beaconFrames(i).BSSID); - interval = string(beaconFrames(i).MAC_Config.ManagementConfig.BeaconInterval); + bssid = toMACAddress(beaconFrames(i).BSSID); + interval = string(mgmt.BeaconInterval); snr = sprintf("%.1f", beaconFrames(i).SNR_dB); - bch = "-"; - opch = "-"; - vendor = "-"; - std = "-"; - rates = "-"; + [bch, opch] = extractChannels(mgmt); + vendor = lookupVendor(beaconFrames(i).BSSID); + rates = getRates(mgmt); + std = getWifiStandard(mgmt); - fprintf("%-20s %-18s %-10s %-8s %-8s %-8s %-12s %-8s %-20s\n", ... + fprintf(tableFormat, ... ssid, bssid, interval, bch, opch, snr, vendor, std, rates); end fprintf("\n"); +end + +function mac = toMACAddress(mac) + mac = upper(string(mac)); + + if strlength(mac) ~= 12 + return; + end + + macChars = char(mac); + + mac = string(sprintf("%s:%s:%s:%s:%s:%s", ... + macChars(1:2), ... + macChars(3:4), ... + macChars(5:6), ... + macChars(7:8), ... + macChars(9:10), ... + macChars(11:12))); +end + +function rates = getRates(mgmt) + basicRates = string(mgmt.BasicRates); + additionalRates = string(mgmt.AdditionalRates); + + if isempty(basicRates) && isempty(additionalRates) + rates = "-"; + return; + end + + basicRates = "[" + strjoin(basicRates, ", ") + "]"; + rates = strjoin([basicRates, additionalRates], ", "); +end + +function vendor = lookupVendor(bssid) + bssid = upper(string(bssid)); + + if strlength(bssid) < 6 + vendor = "-"; + return; + end + + oui = extractBefore(bssid, 7); + + switch oui + case { "C884A1", "5CE176" } + vendor = "Cisco"; + case { "6466B3", "647002" } + vendor = "TP-Link"; + otherwise + vendor = "Unknown"; + end +end + +function std = getWifiStandard(mgmt) + rates = [string(mgmt.BasicRates), string(mgmt.AdditionalRates)]; + + if any(contains(rates, ["6 Mbps", "9 Mbps", "12 Mbps", "18 Mbps", "24 Mbps", "36 Mbps", "48 Mbps", "54 Mbps"])) + std = "802.11g"; + elseif any(contains(rates, ["1 Mbps", "2 Mbps", "5.5 Mbps", "11 Mbps"])) + std = "802.11b"; + else + std = "Unknown"; + end +end + +function [b_ch, op_ch] = extractChannels(mgmt) + b_ch = "-"; + op_ch = "-"; + + ies = mgmt.InformationElements; + + if(isempty(ies)) + return; + end + + for i = 1 : size(ies, 1) + id = ies{i, 1}; + value = ies{i, 2}; + + if isempty(id) || isempty(value) + continue; + end + + switch id(1) + case 3 + b_ch = string(double(value(1))); + case 61 + op_ch = string(double(value(1))); + end + + if ~strcmp(b_ch, "-") && ~strcmp(op_ch, "-") + return; + end + end end \ No newline at end of file