commit a81abc2695d7a8e21897685f1702395ec0bb6687 Author: v.chau Date: Thu May 15 19:59:14 2025 +0200 based on FT23-Ultra script. fixed for FT24 and FT25 diff --git a/assets/FT_icon.png b/assets/FT_icon.png new file mode 100644 index 0000000..0adb74b Binary files /dev/null and b/assets/FT_icon.png differ diff --git a/assets/FT_logo.png b/assets/FT_logo.png new file mode 100644 index 0000000..5509d7b Binary files /dev/null and b/assets/FT_logo.png differ diff --git a/assets/loadvector.m b/assets/loadvector.m new file mode 100644 index 0000000..6a5e5d0 --- /dev/null +++ b/assets/loadvector.m @@ -0,0 +1,79 @@ +function [pltData,laps_strArray] = loadvector(filepath) + + % Load dataset: + fprintf("Loading: %s\n",filepath) + data = vector2timetable(filepath,"ABX*","Shunt*","AMS_Status*","INV*","XSens*"); + fprintf("Finished loading\n------------------------\n") + + % get date / time + date = dateshift(data.Time(1),'start','day'); + fprintf("Date: %s\n",date) + fprintf("Start: %d:%d:%02.0f\n",hour(data.Time(1)),minute(data.Time(1)),second(data.Time(1))) + fprintf("End: %d:%d:%02.0f\n",hour(data.Time(end)),minute(data.Time(end)),second(data.Time(end))) + fprintf("------------------------\n") + + % get lapcounter / laptrigger + laps_counter = data.ABX_Driver_ABX_Lapcounter; + timestamp_hms(1) = data.Time(1) - date; + timestamp_index(1) = 1; + j = 2; + for i = 2:length(laps_counter) + if laps_counter(i) ~= laps_counter(i-1) + timestamp_hms(j) = data.Time(i) - date; + timestamp_index(j) = i; + j = j + 1; + end + end + + % calculate laptimes + timestamp_s = seconds(timestamp_hms); + for i = 1:length(timestamp_s)-1 + laps_s(i) = timestamp_s(i+1) - timestamp_s(i); + end + + % if laps could be detected find fastest lap and create list of + % laptimes, otherwise only "Entire stint" will be selectable + laps_strArray(1) = sprintf("Entire stint"); + if (exist("laps_s","var") == true) + % create list of laptimes + for i = 1:length(laps_s) + if laps_s(i) == min(laps_s) + laps_strArray(i+1) = sprintf("Lap %d: %.3fs (best)",i,laps_s(i)); + else + laps_strArray(i+1) = sprintf("Lap %d: %.3fs",i,laps_s(i)); + end + end + % display fastest lap + [laps_best_time, laps_best] = min(laps_s); + fprintf("Fastest lap: %.2fs (%d)\n",laps_best_time,laps_best) + end + + % create time arrays in hh:mm:ss and just seconds + time_hms = data.Time-date; + time_s = seconds(time_hms); + time_s = time_s - time_s(1); % set beginning of session to 0 + + % calcualte energy consumption and total distance + power_kw = data.Shunt_Voltage1_Shunt_Voltage1.*data.Shunt_Current_Shunt_Current/1000; + power_regen_kw = power_kw; + power_regen_kw(power_regen_kw > 0) = 0; + power_used_kw = power_kw; + power_used_kw(power_used_kw < 0) = 0; + energy_kwh = trapz(time_s, power_kw)/3600; + energy_regen_kwh = trapz(time_s, power_regen_kw)/3600; + energy_used_kwh = trapz(time_s, power_used_kw)/3600; + fprintf("Energy used: %.2fkWh\n",energy_used_kwh) + fprintf("Energy regen: %.2fkWh\n",energy_regen_kwh) + fprintf("Energy total: %.2fkWh\n",energy_kwh) + distance_km = trapz(time_s, movmean(data.ABX_Driver_ABX_Speed,10))/1000; + fprintf("Distance driven: %.2fkm\n",distance_km) + fprintf("------------------------\n") + + pltData(1) = sortdata(data,1,length(data.Time)); + % sort data for plotting: + for i = 1:length(timestamp_index)-1 + pltData(i+1) = sortdata(data,timestamp_index(i),timestamp_index(i+1)); + end + +end + diff --git a/assets/savePlot.m b/assets/savePlot.m new file mode 100644 index 0000000..55c9de9 --- /dev/null +++ b/assets/savePlot.m @@ -0,0 +1,21 @@ +function [outputArg] = savePlot(figure,path,format) + + switch format + case "png" + savepath = sprintf("%s.png",path); + exportgraphics(figure,savepath); + case "pdf" + savepath = sprintf("%s.pdf",path); + exportgraphics(figure,savepath,'ContentType','vector'); + case "fig" + saveas(figure,path,"fig"); + case "m" + saveas(figure,path,"m"); + otherwise + error("invalid filetype"); + end + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/assets/sortdata.m b/assets/sortdata.m new file mode 100644 index 0000000..9832753 --- /dev/null +++ b/assets/sortdata.m @@ -0,0 +1,122 @@ +function [pltData] = sortdata(data,start,stop) + date = dateshift(data.Time(1),'start','day'); + time_hms = data.Time-date; + time_s = seconds(time_hms); + time_s = time_s - time_s(1); % set beginning of session to 0 + + % x-Axis: + pltData.time_s = time_s(start:stop)-time_s(start); + pltData.time_hms = time_hms(start:stop); + + % distance calculation + speed_mps = movmean(data.ABX_Driver_ABX_Speed(start:stop),50); % TODO: test different speed filters for distance calculation + pltData.distance(1) = 0; + for i = 2:length(speed_mps) + pltData.distance(i) = speed_mps(i)*(pltData.time_s(i)-pltData.time_s(i-1)) + pltData.distance(i-1); + end + pltData.xAxis = pltData.time_s; % set time as default x-Axis for plotting + + %% Misc + pltData.app_percent = data.ABX_Driver_ABX_APPS_percent(start:stop); + pltData.speed_kph = 3.6*movmean(data.ABX_Driver_ABX_Speed(start:stop),50); % same filter as for distance calculation ??? + pltData.steering_deg = data.ABX_Driver_ABX_Steering_Angle(start:stop); + + %% AMS: + pltData.ams_soc = data.AMS_Status_SOC(start:stop); + pltData.ams_tmax = data.AMS_Status_Max_cell_temp(start:stop); + pltData.ams_utot = data.Shunt_Voltage1_Shunt_Voltage1(start:stop); + pltData.ams_itot = data.Shunt_Current_Shunt_Current(start:stop); + % calculations: + pltData.ams_ptot = pltData.ams_utot.*pltData.ams_itot/1000; + + %% Brakes + % brake pressure + pltData.brakePFront_bar = data.ABX_Driver_ABX_BrakeP_F(start:stop); + pltData.brakePRear_bar = data.ABX_Driver_ABX_BrakeP_R(start:stop); + % brake disc temperatures + pltData.brakeTFrontLeft_degC = data.ABX_BrakeT_ABX_BrakeT_FL(start:stop); + pltData.brakeTFrontRight_degC = data.ABX_BrakeT_ABX_BrakeT_FR(start:stop); + pltData.brakeTRearLeft_degC = data.ABX_BrakeT_ABX_BrakeT_RL(start:stop); + pltData.brakeTRearRight_degC = data.ABX_BrakeT_ABX_BrakeT_RR(start:stop); + % calculate brake bias [%] + minBrakeP = 5; % minimum brake pressure to avoid artifacts due to sensor noise near 0 bar + brakePFront = pltData.brakePFront_bar; + brakePFront(brakePFront < minBrakeP) = minBrakeP; + brakePRear = pltData.brakePRear_bar; + brakePRear(brakePRear < minBrakeP) = minBrakeP; + pltData.brakeBias_perc = 100*brakePFront./brakePRear; % check calculation! + + %% Cooling system + + %% Dampers + % damper positions + pltData.damper_FL_mm = data.ABX_Dampers_ABX_Damper_FL(start:stop); %Heave_F + pltData.damper_FR_mm = data.ABX_Dampers_ABX_Damper_FR(start:stop); %Roll_F + pltData.damper_RL_mm = data.ABX_Dampers_ABX_Damper_RL(start:stop); %Heave_R + pltData.damper_RR_mm = data.ABX_Dampers_ABX_Damper_RR(start:stop); %Roll_R + % calculate damper velocities + pltData.velocity_FL_mmps(1) = 0; + pltData.velocity_FR_mmps(1) = 0; + pltData.velocity_RL_mmps(1) = 0; + pltData.velocity_RR_mmps(1) = 0; + for i = 2:length(pltData.time_s) + timestep = pltData.time_s(i)-pltData.time_s(i-1); + pltData.velocity_FL_mmps(i) = pltData.damper_FL_mm(i)-pltData.damper_FL_mm(i-1)/timestep; + pltData.velocity_FR_mmps(i) = pltData.damper_FR_mm(i)-pltData.damper_FR_mm(i-1)/timestep; + pltData.velocity_RL_mmps(i) = pltData.damper_RL_mm(i)-pltData.damper_RL_mm(i-1)/timestep; + pltData.velocity_RR_mmps(i) = pltData.damper_RR_mm(i)-pltData.damper_RR_mm(i-1)/timestep; + end + % filter damper velocities ??? bessere Berechnung über mittelwert aus mehreren werten? Vorfilterung? + pltData.velocity_FL_mmps = movmean(pltData.velocity_FL_mmps,100); + pltData.velocity_FR_mmps = movmean(pltData.velocity_FR_mmps,100); + pltData.velocity_RL_mmps = movmean(pltData.velocity_RL_mmps,100); + pltData.velocity_RR_mmps = movmean(pltData.velocity_RR_mmps,100); + + %% IMU + % Acceleration + % pltData.acc_long_g = movmean(data.XSens_Acceleration_XSens_accX(start:stop),100)/9.81; + % pltData.acc_lat_g = movmean(data.XSens_Acceleration_XSens_accY(start:stop),100)/9.81; + % Rate of turn + % pltData.rot_roll_degps = movmean(data.XSens_RateOfTurn_XSens_gyrX(start:stop),100); + % pltData.rot_pitch_degps = movmean(data.XSens_RateOfTurn_XSens_gyrY(start:stop),100); + % pltData.rot_yaw_degps = movmean(data.XSens_RateOfTurn_XSens_gyrZ(start:stop),100); + + %% Inverters + % inverter temperatures + pltData.invL_temp = data.INV_L_TxPDO_1_T_Inv_L(start:stop); + pltData.invR_temp = data.INV_R_TxPDO_1_T_Inv_R(start:stop); + % motor temperatures + pltData.motL_temp = data.INV_L_TxPDO_1_T_Mot_L(start:stop); + pltData.motR_temp = data.INV_R_TxPDO_1_T_Mot_R(start:stop); + % motor velocities + pltData.motL_vel_rpm = 60*data.INV_L_TxPDO_4_Velocity_L(start:stop); + pltData.motR_vel_rpm = 60*data.INV_R_TxPDO_4_Velocity_R(start:stop); + % inverter torque demand + pltData.invL_torqueDemand = data.INV_L_TxPDO_3_DemandedTorque_L(start:stop)/10; % /10 to match autobox torque + pltData.invR_torqueDemand = data.INV_R_TxPDO_3_DemandedTorque_R(start:stop)/10; + % inverter actual torque + pltData.invL_torqueActual = data.INV_L_TxPDO_3_ActualTorque_L(start:stop)/10; + pltData.invR_torqueActual = data.INV_R_TxPDO_3_ActualTorque_R(start:stop)/10; + + %% Wheelspeed + + + + + %% Statistics: + power_regen_kw = pltData.ams_ptot; + power_regen_kw(power_regen_kw > 0) = 0; + power_used_kw = pltData.ams_ptot; + power_used_kw(power_used_kw < 0) = 0; + pltData.energy_kwh = trapz(pltData.time_s, pltData.ams_ptot)/3600; + pltData.energy_regen_kwh = trapz(pltData.time_s, power_regen_kw)/3600; + pltData.energy_used_kwh = trapz(pltData.time_s, power_used_kw)/3600; + pltData.distanceTotal_km = trapz(pltData.time_s, pltData.speed_kph./3.6)/1000; % 1/3.6 for km/h to m/s and /1000 for km output + pltData.peakPower_kw = max(pltData.ams_ptot); + pltData.peakPowerMean_kw = max(movmean(pltData.ams_ptot,500)); + + pltData.maxSpeed_kph = max(pltData.speed_kph); + pltData.startTime = time_hms(start); + pltData.stopTime = time_hms(stop); +end + diff --git a/assets/sortdata_XSens.m b/assets/sortdata_XSens.m new file mode 100644 index 0000000..401ef26 --- /dev/null +++ b/assets/sortdata_XSens.m @@ -0,0 +1,122 @@ +function [pltData] = sortdata(data,start,stop) + date = dateshift(data.Time(1),'start','day'); + time_hms = data.Time-date; + time_s = seconds(time_hms); + time_s = time_s - time_s(1); % set beginning of session to 0 + + % x-Axis: + pltData.time_s = time_s(start:stop)-time_s(start); + pltData.time_hms = time_hms(start:stop); + + % distance calculation + speed_mps = movmean(data.ABX_Driver_ABX_Speed(start:stop),50); % TODO: test different speed filters for distance calculation + pltData.distance(1) = 0; + for i = 2:length(speed_mps) + pltData.distance(i) = speed_mps(i)*(pltData.time_s(i)-pltData.time_s(i-1)) + pltData.distance(i-1); + end + pltData.xAxis = pltData.time_s; % set time as default x-Axis for plotting + + %% Misc + pltData.app_percent = data.ABX_Driver_ABX_APPS_percent(start:stop); + pltData.speed_kph = 3.6*movmean(data.ABX_Driver_ABX_Speed(start:stop),50); % same filter as for distance calculation ??? + pltData.steering_deg = data.ABX_Driver_ABX_Steering_Angle(start:stop); + + %% AMS: + pltData.ams_soc = data.AMS_Status_SOC(start:stop); + pltData.ams_tmax = data.AMS_Status_Max_cell_temp(start:stop); + pltData.ams_utot = data.Shunt_Voltage1_Shunt_Voltage1(start:stop); + pltData.ams_itot = data.Shunt_Current_Shunt_Current(start:stop); + % calculations: + pltData.ams_ptot = pltData.ams_utot.*pltData.ams_itot/1000; + + %% Brakes + % brake pressure + pltData.brakePFront_bar = data.ABX_Driver_ABX_BrakeP_F(start:stop); + pltData.brakePRear_bar = data.ABX_Driver_ABX_BrakeP_R(start:stop); + % brake disc temperatures + pltData.brakeTFrontLeft_degC = data.ABX_BrakeT_ABX_BrakeT_FL(start:stop); + pltData.brakeTFrontRight_degC = data.ABX_BrakeT_ABX_BrakeT_FR(start:stop); + pltData.brakeTRearLeft_degC = data.ABX_BrakeT_ABX_BrakeT_RL(start:stop); + pltData.brakeTRearRight_degC = data.ABX_BrakeT_ABX_BrakeT_RR(start:stop); + % calculate brake bias [%] + minBrakeP = 5; % minimum brake pressure to avoid artifacts due to sensor noise near 0 bar + brakePFront = pltData.brakePFront_bar; + brakePFront(brakePFront < minBrakeP) = minBrakeP; + brakePRear = pltData.brakePRear_bar; + brakePRear(brakePRear < minBrakeP) = minBrakeP; + pltData.brakeBias_perc = 100*brakePFront./brakePRear; % check calculation! + + %% Cooling system + + %% Dampers + % damper positions + pltData.damperHeaveFront_mm = data.ABX_Dampers_ABX_DamperHeave_F(start:stop); + pltData.damperRollFront_mm = data.ABX_Dampers_ABX_DamperRoll_F(start:stop); + pltData.damperHeaveRear_mm = data.ABX_Dampers_ABX_DamperHeave_R(start:stop); + pltData.damperRollRear_mm = data.ABX_Dampers_ABX_DamperRoll_R(start:stop); + % calculate damper velocities + pltData.velocityHeaveFront_mmps(1) = 0; + pltData.velocityRollFront_mmps(1) = 0; + pltData.velocityHeaveRear_mmps(1) = 0; + pltData.velocityRollRear_mmps(1) = 0; + for i = 2:length(pltData.time_s) + timestep = pltData.time_s(i)-pltData.time_s(i-1); + pltData.velocityHeaveFront_mmps(i) = pltData.damperHeaveFront_mm(i)-pltData.damperHeaveFront_mm(i-1)/timestep; + pltData.velocityRollFront_mmps(i) = pltData.damperRollFront_mm(i)-pltData.damperRollFront_mm(i-1)/timestep; + pltData.velocityHeaveRear_mmps(i) = pltData.damperHeaveRear_mm(i)-pltData.damperHeaveRear_mm(i-1)/timestep; + pltData.velocityRollRear_mmps(i) = pltData.damperRollRear_mm(i)-pltData.damperRollRear_mm(i-1)/timestep; + end + % filter damper velocities ??? bessere Berechnung über mittelwert aus mehreren werten? Vorfilterung? + pltData.velocityHeaveFront_mmps = movmean(pltData.velocityHeaveFront_mmps,100); + pltData.velocityRollFront_mmps = movmean(pltData.velocityRollFront_mmps,100); + pltData.velocityHeaveRear_mmps = movmean(pltData.velocityHeaveRear_mmps,100); + pltData.velocityRollRear_mmps = movmean(pltData.velocityRollRear_mmps,100); + + %% IMU + % Acceleration + pltData.acc_long_g = movmean(data.XSens_Acceleration_XSens_accX(start:stop),100)/9.81; + pltData.acc_lat_g = movmean(data.XSens_Acceleration_XSens_accY(start:stop),100)/9.81; + % Rate of turn + pltData.rot_roll_degps = movmean(data.XSens_RateOfTurn_XSens_gyrX(start:stop),100); + pltData.rot_pitch_degps = movmean(data.XSens_RateOfTurn_XSens_gyrY(start:stop),100); + pltData.rot_yaw_degps = movmean(data.XSens_RateOfTurn_XSens_gyrZ(start:stop),100); + + %% Inverters + % inverter temperatures + pltData.invL_temp = data.INV_L_TxPDO_1_T_Inv_L(start:stop); + pltData.invR_temp = data.INV_R_TxPDO_1_T_Inv_R(start:stop); + % motor temperatures + pltData.motL_temp = data.INV_L_TxPDO_1_T_Mot_L(start:stop); + pltData.motR_temp = data.INV_R_TxPDO_1_T_Mot_R(start:stop); + % motor velocities + pltData.motL_vel_rpm = 60*data.INV_L_TxPDO_4_Velocity_L(start:stop); + pltData.motR_vel_rpm = 60*data.INV_R_TxPDO_4_Velocity_R(start:stop); + % inverter torque demand + pltData.invL_torqueDemand = data.INV_L_TxPDO_3_DemandedTorque_L(start:stop)/10; % /10 to match autobox torque + pltData.invR_torqueDemand = data.INV_R_TxPDO_3_DemandedTorque_R(start:stop)/10; + % inverter actual torque + pltData.invL_torqueActual = data.INV_L_TxPDO_3_ActualTorque_L(start:stop)/10; + pltData.invR_torqueActual = data.INV_R_TxPDO_3_ActualTorque_R(start:stop)/10; + + %% Wheelspeed + + + + + %% Statistics: + power_regen_kw = pltData.ams_ptot; + power_regen_kw(power_regen_kw > 0) = 0; + power_used_kw = pltData.ams_ptot; + power_used_kw(power_used_kw < 0) = 0; + pltData.energy_kwh = trapz(pltData.time_s, pltData.ams_ptot)/3600; + pltData.energy_regen_kwh = trapz(pltData.time_s, power_regen_kw)/3600; + pltData.energy_used_kwh = trapz(pltData.time_s, power_used_kw)/3600; + pltData.distanceTotal_km = trapz(pltData.time_s, pltData.speed_kph./3.6)/1000; % 1/3.6 for km/h to m/s and /1000 for km output + pltData.peakPower_kw = max(pltData.ams_ptot); + pltData.peakPowerMean_kw = max(movmean(pltData.ams_ptot,500)); + + pltData.maxSpeed_kph = max(pltData.speed_kph); + pltData.startTime = time_hms(start); + pltData.stopTime = time_hms(stop); +end + diff --git a/assets/vector2timetable.m b/assets/vector2timetable.m new file mode 100644 index 0000000..d6433d0 --- /dev/null +++ b/assets/vector2timetable.m @@ -0,0 +1,21 @@ +function [data] = vector2timetable(data_path, varargin) +%VECTOR2TIMETABLE Load vector datalogger data and convert it to a timetable +% Specify the path to the data in DATA_PATH, and optionally specify names +% of signals to load. + raw_data = load(data_path, varargin{:}); + fn = fieldnames(raw_data); + base_ts = datetime(0, 1, 0); + for i=1:numel(fn) + var = raw_data.(fn{i}); + timestamps = days(var(:,1)) + base_ts; + tt{i} = array2timetable( ... + var(:,2), ... + "RowTimes",timestamps, ... + "VariableNames",{fn{i}} ... + ); + end + data = synchronize(tt{:},"union","nearest"); + % union -> union of the row times + % method -> fill gaps in output with nearest neighbor in the input timetable +end + diff --git a/cut_data.m b/cut_data.m new file mode 100644 index 0000000..b52d186 --- /dev/null +++ b/cut_data.m @@ -0,0 +1,39 @@ +close all +clear all + +% user input: +input_path = "data\TriggerF.mat"; +output_path = "data\TriggerF_cut.mat"; +startTime = [10, 15, 00]; +stopTime = [17, 34, 00]; + +% load data +data_in = load(input_path); +% get time +time = days(data_in.ABX_Driver_ABX_Speed(:,1)) + datetime(0,1,0); +% plot speed for visualization +plot(time,data_in.ABX_Driver_ABX_Speed(:,2)) +grid on +% set start/stop timestamps +date = dateshift(time(1),'start','day'); +date_d = datenum(date)+1; +startTime_d = date_d + convertHMStoDays(startTime); +stopTime_d = date_d + convertHMStoDays(stopTime); + +fn = fieldnames(data_in); +for i=1:numel(fn) + clear var_out + var = data_in.(fn{i}); + index1 = find(var(:,1)>startTime_d,1); + index2 = find(var(:,1)>stopTime_d,1); + var_out(:,1) = var(index1:index2,1); + var_out(:,2) = var(index1:index2,2); + data_out.(fn{i}) = var_out; +end + +save(output_path,"-struct","data_out") + +%% Functions +function time_d = convertHMStoDays(hmsArray) + time_d = hmsArray(1)/24 + hmsArray(2)/(24*60) + hmsArray(3)/(24*60*60); +end \ No newline at end of file diff --git a/data/VL_2024_07_18.mat b/data/VL_2024_07_18.mat new file mode 100644 index 0000000..8021525 Binary files /dev/null and b/data/VL_2024_07_18.mat differ diff --git a/lap_analysis.m b/lap_analysis.m new file mode 100644 index 0000000..d24760b --- /dev/null +++ b/lap_analysis.m @@ -0,0 +1,229 @@ +% Lap analysis script based on vector CAN Data +% ToDo: +% - add Driver - Steering -> Oversteer calculation (steering/acc_lat) +% - fix Driver - Statistics -> Braking distribution to start at >1 bar +% - calculation damper velocities!! +% - add size selection for output graphics +% - add legend for multiple laps selection (instead of statistics?) +% - automatic plot_settings detection?? + +close all +clear all +format shortEng + +%% Add required paths +addpath("assets\"); +addpath("plot_settings\"); +save_path = "plots\"; + +% intital variables: +global pltData; +pltData = 0; +global filepath_last; +filepath_last = "none"; + +%% create uifigure +fig = uifigure; +fig.Position = [100 100 800 600]; +fig.Name = "FT23-Ultra Data Analysis Tool"; +fig.Icon = "assets/FT_icon.png"; + +% create 5x3 grid layout (row,column) +g = uigridlayout(fig); +g.RowHeight = {22, 22,'1x',22,22}; +g.ColumnWidth = {150,'1x',200}; + +% (1,2) Load path +filepathField = uieditfield(g); +filepathField.Value = "data/"; +filepathField.Layout.Row = 1; +filepathField.Layout.Column = 2; +% (2,1) label "Plot type:" +labelPlot = uilabel(g); +labelPlot.Text = "Plot type:"; +labelPlot.Layout.Row = 2; +labelPlot.Layout.Column = 1; +labelPlot.HorizontalAlignment = "right"; +% (2,2) create dropdown selection for plot type +dd = uidropdown(g); +dd.Items = ["Driver - General", "Driver - Steering", "Driver - Braking", "Driver - Statistics" ... + "Powertrain", "Brakes", "Accumulator", "Inverter", ... + "Suspension - Positions", "Suspension - Velocities", "Suspension - Histogram", ... + "Tires - Temperatures", "Tires - Friction Circle"]; +dd.Layout.Row = 2; +dd.Layout.Column = 2; +% ([3 4],1) create list box with laptimes and multiselection +lb = uilistbox(g); +lb.Multiselect = "on"; +lb.Items = "No data loaded!"; +lb.ItemsData = 1; +lb.Layout.Row = [3 4]; +lb.Layout.Column = 1; +% (3,2) create plot window +pltpanel = uipanel(g); +pltpanel.Layout.Row = 3; +pltpanel.Layout.Column = 2; +% (3,3) lap/stint statistics (peak power, max speed, engergy used) +labelStats = uilabel(g); +labelStats.Text = "Statistics:"; +labelStats.Layout.Row = 3; +labelStats.Layout.Column = 3; +labelStats.VerticalAlignment = "top"; +% (4,2) create dropdown selection for x-Axis (time/distance) +ddAxis = uidropdown(g); +ddAxis.Items = ["Time [s]", "Distance [m]", "Time [h:m:s]"]; +ddAxis.ItemsData = linspace(1,3,3); +ddAxis.Layout.Row = 4; +ddAxis.Layout.Column = 2; +% (4,3) ??? + +% (5,1) dropdown selection for plot output format +ddFormat = uidropdown(g); +ddFormat.Items = ["png", "pdf", "fig", "m"]; +ddFormat.ItemsData = ddFormat.Items; +ddFormat.Layout.Row = 5; +ddFormat.Layout.Column = 1; +% (5,2) edit field for output plot name +editPlotName = uieditfield(g); +editPlotName.Value = "plot"; +editPlotName.Layout.Row = 5; +editPlotName.Layout.Column = 2; + +% (1,1) browse button +btnbrowse = uibutton(g,"ButtonPushedFcn", @(src,event) browseButtonPushed(filepathField)); +btnbrowse.Text = "Browse"; +btnbrowse.Layout.Row = 1; +btnbrowse.Layout.Column = 1; +% (1,3) Load button +btnLoad = uibutton(g,"ButtonPushedFcn", @(src,event) loadButtonPushed(lb, filepathField.Value)); +btnLoad.Text = "Load"; +btnLoad.Layout.Row = 1; +btnLoad.Layout.Column = 3; +% (2,3) create button for plotting new selection +btnPlot = uibutton(g,"ButtonPushedFcn", @(src,event) plotButtonPushed(pltpanel, labelStats, lb.Value, dd.Value, ddAxis.Value)); +btnPlot.Text = "Plot"; +btnPlot.Layout.Row = 2; +btnPlot.Layout.Column = 3; +% (5,3) create button for saving displayed plot +btnSave = uibutton(g,"ButtonPushedFcn", @(src,event) saveButtonPushed(pltpanel, append(save_path, editPlotName.Value), ddFormat.Value)); +btnSave.Text = "Save"; +btnSave.Layout.Row = 5; +btnSave.Layout.Column = 3; + +% Plot window FT Logo for style points (and placeholder) +tl = tiledlayout(pltpanel,"vertical"); +pltpanelDefault = nexttile(tl); +[imageData,~,imageAlpha] = imread("assets\FT_logo.png"); +image(imageData, "Parent",pltpanelDefault, "alphadata", imageAlpha); +pltpanelDefault.Visible = "off"; +pltpanelDefault.DataAspectRatio = [1,1,1]; +pltpanelDefault.Toolbar.Visible = "off"; + + +%% Functions + +function plotButtonPushed(panel, labelStats, selected_laps, plottype, xplot) + + global pltData; + + % If complete stint selected -> disable plotting multiple laps + if ismember(1, selected_laps) + selected_laps = 1; + % xplot = 3; % select hh:mm:ss format automatically?? + end + + if size(selected_laps) == 1 + % update statistics + sidebarLabel = sidebar_stats(pltData(selected_laps)); + else + % clear stats + sidebarLabel = "not available in multiselection (yet)"; + end + labelStats.Text = sidebarLabel; + + % change x-Axis based on drop-down selection: + switch xplot + case 2 + for i = 1:length(pltData) + pltData(i).xAxis = pltData(i).distance; + end + case 3 + for i = 1:length(pltData) + pltData(i).xAxis = pltData(i).time_hms; + end + otherwise + for i = 1:length(pltData) + pltData(i).xAxis = pltData(i).time_s; + end + end + + switch plottype + case "Accumulator" + plot_accumulator(panel, selected_laps, pltData); + case "Brakes" + plot_brakes(panel, selected_laps, pltData); + case "Driver - Braking" + plot_driver_braking(panel, selected_laps, pltData); + case "Driver - General" + plot_driver_general(panel, selected_laps, pltData); + case "Driver - Statistics" + plot_driver_statistics(panel, selected_laps, pltData); + case "Driver - Steering" + plot_driver_steering(panel, selected_laps, pltData); + case "Inverter" + plot_inverter(panel, selected_laps, pltData); + case "Powertrain" + plot_powertrain(panel, selected_laps, pltData); + case "Suspension - Histogram" + plot_suspension_histogram(panel, selected_laps, pltData); + case "Suspension - Positions" + plot_suspension_positions(panel, selected_laps, pltData); + case "Suspension - Velocities" + plot_suspension_velocities(panel, selected_laps, pltData); + case "Tires - Friction Circle" + plot_tires_firctionCircle(panel, selected_laps, pltData); + case "Tires - Temperatures" + % plot_tires_temperatures(panel, selected_laps, pltData); + otherwise + error("Error plotting") + end + + % legend(ax1,string(selected_laps),'Location','northeastoutside'); +end + +function loadButtonPushed(listbox, filepath) + global pltData; + global filepath_last; + + % check if data is already loaded + if strcmp(filepath,filepath_last) == false + [pltData,laps_strArray] = loadvector(filepath); + % update UI elements + listbox.Items = laps_strArray; + listbox.ItemsData = linspace(1,length(laps_strArray),length(laps_strArray)); + else + msgbox("Data already loaded!"); + end + + filepath_last = filepath; +end + +function browseButtonPushed(filepathField) + [name, location] = uigetfile(... + {'*.m;*.mat',... + 'MATLAB Files (*.m,*.mat)'; + '*.*', 'All Files (*.*)'}, ... + "Select File","data\"); + filepath = append(location,name); + filepathField.Value = filepath; +end + +function saveButtonPushed(panel, savepath, format) + try + figure = panel.Children; + savePlot(figure,savepath,format); + fprintf("Plot saved to: %s.%s\n",savepath,format); + catch + msgbox("No plot available!") + end +end \ No newline at end of file diff --git a/lap_analysis_guide.pdf b/lap_analysis_guide.pdf new file mode 100644 index 0000000..5a759e1 Binary files /dev/null and b/lap_analysis_guide.pdf differ diff --git a/plot_settings/plot_accumulator.m b/plot_settings/plot_accumulator.m new file mode 100644 index 0000000..e3a8d9a --- /dev/null +++ b/plot_settings/plot_accumulator.m @@ -0,0 +1,43 @@ +function [outputArg] = plot_accumulator(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed [km/h] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: power [kW] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Power [kW]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).ams_ptot) + end + % plot 3: Max Cell Temp [°C] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Max Cell Temp [°C]") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).ams_tmax) + end + % plot 4: State of charge [%] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "SOC") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).ams_soc) + end + % link all x axes + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_brakes.m b/plot_settings/plot_brakes.m new file mode 100644 index 0000000..7f6b922 --- /dev/null +++ b/plot_settings/plot_brakes.m @@ -0,0 +1,59 @@ +function [outputArg] = plot_brakes(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: brake pressure front/rear [bar] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Brake Pressure F/R [bar]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakePFront_bar) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakePRear_bar) + end + legend(ax2, "Front", "Rear") + % plot 3: longitudinal acceleration [g] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Long Acc [g]") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).acc_long_g) + end + % plot 4: Brake Temp [°C] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Brake Temp [°C]") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakeTFrontLeft_degC) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakeTFrontRight_degC) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakeTRearLeft_degC) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakeTRearRight_degC) + end + legend(ax4, "FL", "FR", "RL", "RR") + % plot 5: brake bias [%] + ax5 = nexttile(tl); + hold(ax5, "on") + grid(ax5, "on") + title(ax5, "Brake Bias [%]") + for i = 1:length(selected_laps) + plot(ax5,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakeBias_perc) + end + + % link all x axes + linkaxes([ax1, ax2, ax3, ax4, ax5],"x") + + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_driver_braking.m b/plot_settings/plot_driver_braking.m new file mode 100644 index 0000000..c5643d5 --- /dev/null +++ b/plot_settings/plot_driver_braking.m @@ -0,0 +1,43 @@ +function [outputArg] = plot_driver_braking(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: accelerator pedal position [%] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "APP [%]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).app_percent) + end + % plot 3: brake pressure front [bar] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Brake Pressure Front [bar]") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakePFront_bar) + end + % plot 4: longitudinal acceleration [g] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Long Acc [g]") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).acc_long_g) + end + % link all x axes + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_driver_general.m b/plot_settings/plot_driver_general.m new file mode 100644 index 0000000..fbe881a --- /dev/null +++ b/plot_settings/plot_driver_general.m @@ -0,0 +1,43 @@ +function [outputArg] = plot_driver_general(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: accelerator pedal position [%] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "APP [%]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).app_percent) + end + % plot 3: brake pressure front [bar] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Brake Pressure Front [bar]") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).brakePFront_bar) + end + % plot 4: steering angle [°] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Steering Angle [°]") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).steering_deg) + end + % link all x axes + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_driver_statistics.m b/plot_settings/plot_driver_statistics.m new file mode 100644 index 0000000..04cf635 --- /dev/null +++ b/plot_settings/plot_driver_statistics.m @@ -0,0 +1,46 @@ +function [outputArg] = plot_driver_statistics(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis,pltData(selected_laps(i)).speed_kph) + end + % plot 2: accelerator pedal position [%] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "APP [%]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis,pltData(selected_laps(i)).app_percent) + end + linkaxes([ax1, ax2],"x") + % plot 3: accelerator pedal position histogram + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "APP distribution") + ylabel(ax3, "\sigma [%]") + xlabel(ax3, "APP [%]") + for i = 1:length(selected_laps) + histogram(ax3,pltData(selected_laps(i)).app_percent,"Normalization","percentage") + end + % plot 4: brake pressure histogram + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "BrakeP distribution") + ylabel(ax4, "\sigma [%]") + xlabel(ax4, "Brake Pressure Front [bar]") + for i = 1:length(selected_laps) + histogram(ax4,pltData(selected_laps(i)).brakePFront_bar,"Normalization","percentage") + end + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_driver_steering.m b/plot_settings/plot_driver_steering.m new file mode 100644 index 0000000..d8066ea --- /dev/null +++ b/plot_settings/plot_driver_steering.m @@ -0,0 +1,42 @@ +function [outputArg] = plot_driver_steering(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: steering angle [°] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Steering Angle [°]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).steering_deg) + end + % plot 3: oversteer + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Oversteer") + % TODO!! + + % plot 4: lateral acceleration [g] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Lateral Acc [g]") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).acc_lat_g) + end + % link all x axes + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_inverter.m b/plot_settings/plot_inverter.m new file mode 100644 index 0000000..878f72f --- /dev/null +++ b/plot_settings/plot_inverter.m @@ -0,0 +1,60 @@ +function [outputArg] = plot_inverter(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed [km/h] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: Inverter Temps [°C] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Inverter Temp [°C]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invL_temp) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invR_temp) + end + legend(ax2, "Left", "Right") + % plot 3: Torque request / actual inverter left + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Left Torque") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invL_torqueDemand) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invL_torqueActual) + end + legend(ax3, "Demand", "Actual") + % plot 4: Torque request / actual inverter right + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Right Torque") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invR_torqueDemand) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invR_torqueActual) + end + legend(ax4, "Demand", "Actual") + % plot 5: Motor velocities + ax5 = nexttile(tl); + hold(ax5, "on") + grid(ax5, "on") + title(ax5, "Motor Velocities [1/min]") + for i = 1:length(selected_laps) + plot(ax5,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motL_vel_rpm) + plot(ax5,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motR_vel_rpm) + end + legend(ax5, "Left", "Right") + + % link all x axes + linkaxes([ax1, ax2, ax3, ax4, ax5],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_powertrain.m b/plot_settings/plot_powertrain.m new file mode 100644 index 0000000..dfb51d8 --- /dev/null +++ b/plot_settings/plot_powertrain.m @@ -0,0 +1,58 @@ +function [outputArg] = plot_powertrain(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + + % plot 1: speed [km/h] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Speed [km/h]") + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).speed_kph) + end + % plot 2: power [kW] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Power [kW]") + for i = 1:length(selected_laps) + plot(ax2,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).ams_ptot) + end + % plot 3: Inverter Temps [°C] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Inverter Temp [°C]") + for i = 1:length(selected_laps) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invL_temp) + plot(ax3,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).invR_temp) + end + legend(ax3, "Left", "Right") + % plot 4: Motor Temps + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Motor Temp [°C]") + for i = 1:length(selected_laps) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motL_temp) + plot(ax4,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motR_temp) + end + legend(ax4, "Left", "Right") + % plot 5: Motor velocities + ax5 = nexttile(tl); + hold(ax5, "on") + grid(ax5, "on") + title(ax5, "Motor Velocities [1/min]") + for i = 1:length(selected_laps) + plot(ax5,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motL_vel_rpm) + plot(ax5,pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).motR_vel_rpm) + end + legend(ax5, "Left", "Right") + + % link all x axes + linkaxes([ax1, ax2, ax3, ax4, ax5],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_suspension_histogram.m b/plot_settings/plot_suspension_histogram.m new file mode 100644 index 0000000..fa1e6b4 --- /dev/null +++ b/plot_settings/plot_suspension_histogram.m @@ -0,0 +1,41 @@ +function [outputArg] = plot_suspension_histogram(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + % plot 1: position heave front [mm/s] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Heave Front [mm/s]") + for i = 1:length(selected_laps) + histogram(ax1, pltData(selected_laps(i)).velocityHeaveFront_mmps,"Normalization","percentage") + end + % plot 2: position roll front [mm/s] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Roll Front [mm/s]") + for i = 1:length(selected_laps) + histogram(ax2, pltData(selected_laps(i)).velocityHeaveFront_mmps,"Normalization","percentage") + end + % plot 3: position heave front [mm/s] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Heave Rear [mm/s]") + for i = 1:length(selected_laps) + histogram(ax3, pltData(selected_laps(i)).velocityHeaveFront_mmps,"Normalization","percentage") + end + % plot 4: position roll front [mm/s] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Roll Rear [mm/s]") + for i = 1:length(selected_laps) + histogram(ax4, pltData(selected_laps(i)).velocityHeaveFront_mmps,"Normalization","percentage") + end + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_suspension_positions.m b/plot_settings/plot_suspension_positions.m new file mode 100644 index 0000000..844242f --- /dev/null +++ b/plot_settings/plot_suspension_positions.m @@ -0,0 +1,41 @@ +function [outputArg] = plot_suspension_positions(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + % plot 1: position heave front [mm] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Heave Front [mm]") + for i = 1:length(selected_laps) + plot(ax1, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).damperHeaveFront_mm) + end + % plot 2: position roll front [mm] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Roll Front [mm]") + for i = 1:length(selected_laps) + plot(ax2, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).damperRollFront_mm) + end + % plot 3: position heave front [mm] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Heave Rear [mm]") + for i = 1:length(selected_laps) + plot(ax3, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).damperHeaveRear_mm) + end + % plot 4: position roll front [mm] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Roll Rear [mm]") + for i = 1:length(selected_laps) + plot(ax4, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).damperRollRear_mm) + end + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_suspension_velocities.m b/plot_settings/plot_suspension_velocities.m new file mode 100644 index 0000000..aed80e5 --- /dev/null +++ b/plot_settings/plot_suspension_velocities.m @@ -0,0 +1,41 @@ +function [outputArg] = plot_suspension_velocities(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + tl = tiledlayout(panel,"vertical"); + % plot 1: velocity heave front [mm/s] + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Heave Front [mm/s]") + for i = 1:length(selected_laps) + plot(ax1, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).velocityHeaveFront_mmps) + end + % plot 2: velocity roll front [mm/s] + ax2 = nexttile(tl); + hold(ax2, "on") + grid(ax2, "on") + title(ax2, "Roll Front [mm/s]") + for i = 1:length(selected_laps) + plot(ax2, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).velocityRollFront_mmps) + end + % plot 3: velocity heave front [mm/s] + ax3 = nexttile(tl); + hold(ax3, "on") + grid(ax3, "on") + title(ax3, "Heave Rear [mm/s]") + for i = 1:length(selected_laps) + plot(ax3, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).velocityHeaveRear_mmps) + end + % plot 4: velocity roll front [mm/s] + ax4 = nexttile(tl); + hold(ax4, "on") + grid(ax4, "on") + title(ax4, "Roll Rear [mm/s]") + for i = 1:length(selected_laps) + plot(ax4, pltData(selected_laps(i)).xAxis, pltData(selected_laps(i)).velocityRollRear_mmps) + end + linkaxes([ax1, ax2, ax3, ax4],"x") + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/plot_tires_firctionCircle.m b/plot_settings/plot_tires_firctionCircle.m new file mode 100644 index 0000000..baa117e --- /dev/null +++ b/plot_settings/plot_tires_firctionCircle.m @@ -0,0 +1,24 @@ +function [outputArg] = plot_tires_firctionCircle(panel, selected_laps, pltData) + % create tiledlayout (R2023a and newer) + + tl = tiledlayout(panel,"flow"); + + ax1 = nexttile(tl); + hold(ax1, "on") + grid(ax1, "on") + title(ax1, "Friction Circle") + ylabel(ax1, "Longitudinal Acc [g]") + xlabel(ax1, "Lateral Acc [g]") + colors = colororder(ax1); + for i = 1:length(selected_laps) + plot(ax1,pltData(selected_laps(i)).acc_lat_g, ... + pltData(selected_laps(i)).acc_long_g,"Color",colors(i,:)) + K = convhull(pltData(selected_laps(i)).acc_lat_g, pltData(selected_laps(i)).acc_long_g); + plot(ax1,pltData(selected_laps(i)).acc_lat_g(K), ... + pltData(selected_laps(i)).acc_long_g(K),"Color",colors(i,:),"LineStyle","--","LineWidth",2) + end + + % return null (not relevant for plots!) + outputArg = []; +end + diff --git a/plot_settings/sidebar_stats.m b/plot_settings/sidebar_stats.m new file mode 100644 index 0000000..018b9b8 --- /dev/null +++ b/plot_settings/sidebar_stats.m @@ -0,0 +1,21 @@ +function [sidebarLabel] = sidebar_stats(pltData) +%SIDEBAR_STATS Summary of this function goes here +% Detailed explanation goes here + sidebarLabel = sprintf(... + "Statistics:\nEnergy\n Used: %.2f kWh\n" + ... + " Regen: %.2f kWh\n" + ... + " Total: %.2f kWh\n" + ... + "\nPower\n Peak: %.1f kW\n" + ... + " Peak (mean): %1.f kW\n" + ... + "\nDistance: %.2f km\n" + ... + "\nTopspeed: %.1f km/h\n", ... + pltData.energy_used_kwh, ... + pltData.energy_regen_kwh, ... + pltData.energy_kwh, ... + pltData.peakPower_kw, ... + pltData.peakPowerMean_kw, ... + pltData.distanceTotal_km, ... + pltData.maxSpeed_kph ... + ); +end + diff --git a/set_timestamps.m b/set_timestamps.m new file mode 100644 index 0000000..e69de29