diff --git a/Classes/00_signals/Signal.m b/Classes/00_signals/Signal.m index 872b9d3..ff6e05b 100644 --- a/Classes/00_signals/Signal.m +++ b/Classes/00_signals/Signal.m @@ -172,9 +172,9 @@ classdef Signal hold on; if isempty(options.color) - plot(t, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1); + plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1); else - plot(t, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color); + plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color); end % 2 c) % - xlabel if not already here: time in readable format (1 ms and not 1e-3 s) @@ -187,10 +187,7 @@ classdef Signal ylabel('Amplitude'); end - % Convert time axis to milliseconds for readability - xticks = get(gca, 'XTick'); - set(gca, 'XTick', xticks, 'XTickLabel', xticks * 1e6); - + % Add legend if not already present if isempty(get(gca, 'Legend')) legend; @@ -656,7 +653,7 @@ classdef Signal end %% - function [obj,S,isFlipped,sequenceFound] = tsynch(obj,options) + function [obj,S,inverted,sequenceFound,sequenceStarts] = tsynch(obj,options) % time sync and cut arguments obj Signal @@ -668,9 +665,9 @@ classdef Signal S = {}; - isFlipped=0; + inverted = -1; sequenceFound = 0; - + sequenceStarts = []; %normalize the signal @@ -711,19 +708,19 @@ classdef Signal shifts = lags(pkpos); - + sequenceStarts = shifts; % shifts = shifts(shifts>=0); if numel(shifts) > 0 %Cut occurences of ref signal from signal (only positive shifts) - if all(sign(co(pkpos))) - isFlipped = 1; + if all(sign(co(pkpos))==-1) + inverted = 1; end for c = shifts sig = obj.delay(-c,'mode','samples'); - sig.signal = sig.signal(1:length(b)).*-isFlipped; + sig.signal = sig.signal(1:length(b)) .* -inverted; S{end+1,1} = sig; end diff --git a/Classes/DataBaseHandler/DBHandler.m b/Classes/DataBaseHandler/DBHandler.m index 6f90ca7..5e8333e 100644 --- a/Classes/DataBaseHandler/DBHandler.m +++ b/Classes/DataBaseHandler/DBHandler.m @@ -9,6 +9,7 @@ classdef DBHandler < handle tableNames % Cell array containing names of all tables in the database tables = struct(); % Structure containing MATLAB tables for each database table distinctValues + type end methods @@ -21,6 +22,7 @@ classdef DBHandler < handle arguments options.pathToDB = ""; % Default value for pathToDB if not provided + options.type = "mysql"; end % Assign values to class properties based on input arguments @@ -33,7 +35,22 @@ classdef DBHandler < handle % Establish a connection to the SQLite database try - obj.conn = sqlite(obj.pathToDB); + + if options.type == "sqlite" + obj.conn = sqlite(obj.pathToDB); + elseif options.type == "mysql" + datasource = "jdbc:mysql://134.245.243.254:3306/labor"; + + obj.conn = database( ... + "labor", ... % Database name + "silas", ... % Username + "silas", ... % Password (or getSecret) + "Vendor", "MySQL", ... + "Server", "134.245.243.254", ... + "PortNumber", 3306, ... + "JDBCDriverLocation", "C:\Users\Silas\Documents\mysql-connector-j-9.3.0\mysql-connector-j-9.3.0.jar"); + end + catch e error('Failed to connect to the database: %s', e.message); end @@ -59,8 +76,14 @@ classdef DBHandler < handle function obj = getTableNames(obj) % Get all table names from the database try - result = fetch(obj.conn, 'SELECT name FROM sqlite_master WHERE type="table"'); - obj.tableNames = result.name; + if obj.type == "mysql" + result = fetch(obj.conn, 'SHOW TABLES;'); + obj.tableNames = result.Variables; + elseif obj.type == "sqlite" + result = fetch(obj.conn, 'SELECT name FROM sqlite_master WHERE type="table"'); + obj.tableNames = result.name; + end + catch e error('Failed to retrieve table names: %s', e.message); end @@ -213,12 +236,16 @@ classdef DBHandler < handle elseif ischar(value) newRow.(colName{1}) = string(value); + elseif isdatetime(value) + + % newRow.(colName{1}) = string(value); + end end % Parameters for retry logic - maxRetries = 5; + maxRetries = 50; basePause = 0.05; % seconds attempt = 0; @@ -226,6 +253,9 @@ classdef DBHandler < handle while ~success && attempt <= maxRetries try + if obj.type == "mysql" + newRow_ = obj.convertTableToCellStrings(newRow); + end sqlwrite(obj.conn, tableName, newRow); success = true; % Write successful catch e @@ -245,7 +275,14 @@ classdef DBHandler < handle end % Retrieve the measurement_id of the newly inserted row for linking other tables - result = fetch(obj.conn, 'SELECT last_insert_rowid()'); + + if obj.type == "mysql" + queue = "SELECT LAST_INSERT_ID()"; + elseif obj.type == "sqlite" + queue = "SELECT last_insert_rowid()"; + end + + result = fetch(obj.conn, queue); lastID = result{1, 1}; % Access the value directly from the table end @@ -502,8 +539,87 @@ classdef DBHandler < handle % Step 4: Execute the query and handle results result = obj.fetch(query); + result = obj.normalizeMySQLTable(result); end + function cleanedTable = normalizeMySQLTable(~,result) + cleanedTable = result; + varNames = result.Properties.VariableNames; + + for i = 1:numel(varNames) + col = result.(varNames{i}); + + % Only process if column is a cell array of strings or chars + if iscell(col) && all(cellfun(@(x) ischar(x) || isstring(x), col)) + % Try converting to numeric if possible + numCol = str2double(col); + if all(~isnan(numCol) | strcmpi(col, 'NaN')) + % It's numeric (with possible NaNs) + cleanedTable.(varNames{i}) = numCol; + else + % Clean double-quoted SQL literals (e.g., ""no_db"") + cleanedTable.(varNames{i}) = strrep(string(col), '""', '"'); + end + end + end + end + + function cellTable = convertTableToCellStrings(~,tbl) + % Converts all variables in a MATLAB table to cell arrays of strings (like JDBC fetch from MySQL) + % + % Example: + % jdbcFormatted = convertTableToCellStrings(myTable); + + cellTable = tbl; + varNames = tbl.Properties.VariableNames; + + for i = 1:numel(varNames) + col = tbl.(varNames{i}); + + if isnumeric(col) + % Convert numeric values to strings + cellTable.(varNames{i}) = arrayfun(@(x) num2str(x, '%.15g'), col, 'UniformOutput', false); + + elseif isstring(col) || ischar(col) + % Ensure cell array of strings + cellTable.(varNames{i}) = cellstr(col); + + elseif iscell(col) + % Convert each cell entry to string + cellTable.(varNames{i}) = cellfun(@convertToString, col, 'UniformOutput', false); + + elseif islogical(col) + % Convert logicals to '0' or '1' + cellTable.(varNames{i}) = cellstr(string(double(col))); + + elseif isdatetime(col) + % Convert datetimes to formatted string + cellTable.(varNames{i}) = cellstr(string(col)); + + else + warning('Column "%s" has unsupported type. Converting using string().', varNames{i}); + cellTable.(varNames{i}) = cellstr(string(col)); + end + end + end + + function out = convertToString(~,val) + if ischar(val) + out = val; + elseif isstring(val) + out = char(val); + elseif isnumeric(val) + out = num2str(val, '%.15g'); + elseif islogical(val) + out = num2str(double(val)); + elseif isdatetime(val) + out = datestr(val, 'yyyy-mm-dd HH:MM:SS'); + else + out = char(string(val)); % fallback + end + end + + function query = constructSQLQuery(obj, filterParams, selectedFields) % constructSQLQuery Constructs the SQL query based on filter parameters and selected fields. % diff --git a/Functions/EQ_structures/vnle_postfilter_mlse.m b/Functions/EQ_structures/vnle_postfilter_mlse.m index 70c95af..d125ca6 100644 --- a/Functions/EQ_structures/vnle_postfilter_mlse.m +++ b/Functions/EQ_structures/vnle_postfilter_mlse.m @@ -99,6 +99,8 @@ end [gmi_vnle] = calc_air(eq_signal_sd,tx_symbols,"skip_front",10000,"skip_end",10000); air_vnle = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_vnle ./ log2(double(M)); [evm_vnle_total,evm_vnle_lvl] = calc_evm(eq_signal_sd,tx_symbols); +[std_vnle_total,std_vnle_lvl] = calc_std(eq_signal_sd,tx_symbols); +[std_rxraw_total,std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out",tx_symbols.fs),tx_symbols); % METRICS OF MLSE (HD-VITERBI) pf_.ncoeff = 1; @@ -127,6 +129,10 @@ resultsVNLE = struct( ... 'numBitErr_precoded', errors_vnle_diff_precoded, ... % Beispiel: 120 Bitfehler 'SNR', snr_vnle, ... % Beispielhafte SNR 'SNR_level', jsonencode(snr_vnle_lvl), ... % SNR-Level als JSON-codiertes Array + 'STD', std_vnle_total, ... + 'STD_level', jsonencode(std_vnle_lvl),... + 'STDrx' , std_rxraw_total, ... + 'STDrx_level', jsonencode(std_rxraw_lvl),... 'GMI', gmi_vnle, ... % Beispielhafter GMI-Wert 'AIR', air_vnle, ... % Beispielhafter AIR-Wert 'EVM', evm_vnle_total, ... % Beispielhafte EVM @@ -234,9 +240,12 @@ eq_package.resultsMLSE = resultsMLSE; eq_package.equalizerConfigVNLE = equalizerConfigVNLE; eq_package.equalizerConfigMLSE = equalizerConfigMLSE; + + % eq_package.vnle_out = eq_signal_sd; if options.showAnalysis + % fprintf(['VNLE EVM lvl: ',repmat('%.3f ',1,numel(evm_lvl)),' \n'],evm_lvl); % fprintf('VNLE BER: %.2e \n',ber_vnle); @@ -264,6 +273,10 @@ if options.showAnalysis figure(341);clf; showLevelHistogram(eq_signal_sd,tx_symbols,"fignum",341); + showLevelScatter(eq_signal_sd,tx_symbols,"fignum",400); + + showLevelScatter(rx_signal.resample("fs_out",tx_symbols.fs),tx_symbols,"fignum",401); + % autoArrangeFigures(3,3,2) diff --git a/Functions/EQ_visuals/showLevelScatter.m b/Functions/EQ_visuals/showLevelScatter.m new file mode 100644 index 0000000..b7cab41 --- /dev/null +++ b/Functions/EQ_visuals/showLevelScatter.m @@ -0,0 +1,129 @@ +function showLevelScatter(eq_signal,ref_symbols,options) +arguments + eq_signal + ref_symbols + options.fignum (1,1) double = NaN % Default to NaN if not provided + options.displayname (1,:) char = '' % Default to an empty string if not provided + options.f_sym = []; +end + +if isa(eq_signal,'Signal') + options.f_sym = eq_signal.fs; + eq_signal = eq_signal.signal; +end +if isa(ref_symbols,'Signal') + ref_symbols = ref_symbols.signal; +end + +% Determine the figure number to use or create a new figure +if isnan(options.fignum) + fig = figure; % Create a new figure and get its handle +else + fig = figure(options.fignum); % Use the specified figure number + clf; +end + +assert(~isempty(options.f_sym),'No fsym given'); + +rx_symbols = eq_signal ./ rms(eq_signal); +correct_symbols = ref_symbols; +f_sym = options.f_sym; + +col = cbrewer2('Paired',numel(unique(correct_symbols))*2); +ccnt = -1; + +levels = unique(correct_symbols); + +start = 1; +ende = length(correct_symbols); + +% start = 30000; +% ende = 40000; + +for l = 1:4 + ccnt = ccnt+2; + + level_amplitude = levels(l); + + symbols_for_lvl = NaN(1,length(correct_symbols)); + symbols_for_lvl(correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude); + std_lvl(l) = std(symbols_for_lvl,'omitnan'); + xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6; + % xax_in_sec = 1:length(correct_symbols); + + scatter(xax_in_sec(start:ende),symbols_for_lvl(start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:)); + hold on; +end + +std_lvl = round(std_lvl,2); + +ccnt = 0; + +% Add the windowed/ smoothed curves +for l = 1:4 + ccnt = ccnt+2; + level_amplitude = levels(l); + + symbols_for_lvl = NaN(1,length(correct_symbols)); + + movmean = 1/250 .* movsum(rx_symbols(correct_symbols==level_amplitude),[250/2,250/2]); + + symbols_for_lvl(correct_symbols==level_amplitude) = movmean; + + nanx = isnan(symbols_for_lvl); + t = 1:numel(symbols_for_lvl); + symbols_for_lvl(nanx) = interp1(t(~nanx), symbols_for_lvl(~nanx), t(nanx)); + + xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6; + % xax_in_sec = 1:length(correct_symbols); + + plot(xax_in_sec(start:ende),symbols_for_lvl(start:ende),'Color',col(ccnt,:)); + hold on +end + +%yline(max(rx_symbols(correct_symbols==levels(2)))) +yline(levels); + +if 0 + annotation(fig,'textbox',... + [0.660523809523809 0.844444444444448 0.133523809523809 0.0603174603174607],... + 'String',['\sigma = ',num2str(std_lvl(4))],... + 'LineWidth',1.8,... + 'LineStyle','none',... + 'FontSize',12,... + 'FitBoxToText','off'); + + % Create textbox + annotation(fig,'textbox',... + [0.667666666666665 0.642857142857147 0.133523809523809 0.0603174603174607],... + 'String',['\sigma = ',num2str(std_lvl(3))],... + 'LineWidth',1.8,... + 'LineStyle','none',... + 'FontSize',12,... + 'FitBoxToText','off'); + + % Create textbox + annotation(fig,'textbox',... + [0.671238095238093 0.442857142857148 0.133523809523809 0.0603174603174608],... + 'String',['\sigma = ',num2str(std_lvl(2))],... + 'LineWidth',1.8,... + 'LineStyle','none',... + 'FontSize',12,... + 'FitBoxToText','off'); + + % Create textbox + annotation(fig,'textbox',... + [0.670047619047616 0.265079365079371 0.133523809523809 0.0603174603174608],... + 'String',['\sigma = ',num2str(std_lvl(1))],... + 'LineWidth',1.8,... + 'LineStyle','none',... + 'FontSize',12,... + 'FitBoxToText','off'); +end + +% xlim([0, 2.6]) +% ylim([-2 2]) +xlabel('Time in $\mu$s'); +ylabel('Normalized Amplitude'); + +end diff --git a/Functions/Metrics/calc_std.m b/Functions/Metrics/calc_std.m new file mode 100644 index 0000000..5f3a2e5 --- /dev/null +++ b/Functions/Metrics/calc_std.m @@ -0,0 +1,68 @@ +function [std_total,std_lvl] = calc_std(test_signal,reference_signal,options) + +arguments(Input) + test_signal; + reference_signal; + options.skip_front = 0; + options.skip_end = 0; + options.returnErrorLocation = 0; +end + +options.skip_end = abs(options.skip_end); +options.skip_front = abs(options.skip_front); + +assert((options.skip_end+options.skip_front)