Task 2 should be finished

This commit is contained in:
ti_mo
2026-05-22 19:16:08 +02:00
parent 2fa2bf7f9a
commit 0df0c5da43
4 changed files with 591 additions and 41 deletions
+146
View File
@@ -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
+70 -14
View File
@@ -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];
@@ -129,3 +181,7 @@ if ~calculations_only
disp("Busiest channel: " + busiest_channel)
end
function val = linearTodB(toConvert)
val = 10 * log10(toConvert);
end
+218 -7
View File
@@ -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);
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
+155 -18
View File
@@ -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