based on FT23-Ultra script. fixed for FT24 and FT25

This commit is contained in:
2025-05-15 19:59:14 +02:00
commit a81abc2695
25 changed files with 1195 additions and 0 deletions

BIN
assets/FT_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/FT_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

79
assets/loadvector.m Normal file
View File

@ -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

21
assets/savePlot.m Normal file
View File

@ -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

122
assets/sortdata.m Normal file
View File

@ -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

122
assets/sortdata_XSens.m Normal file
View File

@ -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

21
assets/vector2timetable.m Normal file
View File

@ -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