mirror of https://github.com/ArduPilot/ardupilot
504 lines
19 KiB
Matlab
504 lines
19 KiB
Matlab
% Reads ArduPilot's system identification dataflash logs and extracts the SID
|
|
% subsections of the fligts into .mat file, discarding all irrelevant data
|
|
%
|
|
%
|
|
% Fabian Bredemeier - IAV GmbH
|
|
% License: GPL v3
|
|
|
|
close all;
|
|
|
|
%% File selection by user
|
|
|
|
% ---------------------------------------------- %
|
|
% %
|
|
% File selection %
|
|
% %
|
|
% ---------------------------------------------- %
|
|
|
|
% 1) Ask user if next data should be added to existing data set
|
|
if exist('sidLogs', 'var')
|
|
% Abort if config data for existing data is missing
|
|
if ~exist('loadedDataConfig', 'var')
|
|
error('No configuration of the existing data could be found. Aborting...');
|
|
end
|
|
addData = input('Add next data to existing data set (0: no, 1: yes)?');
|
|
if addData > 1 && ~isnumeric(addData)
|
|
error('Input not valid. Only 0 or 1 are allowed.')
|
|
end
|
|
else
|
|
addData = 0;
|
|
end
|
|
|
|
% Delete old data of new data will not be added. Otherwise get number of
|
|
% existing elements
|
|
if ~addData
|
|
clear sidLogs idData
|
|
numOldSubflights = 0;
|
|
numOldSysidflights = 0;
|
|
else
|
|
numOldSubflights = numel(sidLogs);
|
|
if exist('idData', 'var')
|
|
numOldSysidflights = numel(idData);
|
|
end
|
|
end
|
|
|
|
% 2) User chooses new flight data from explorer
|
|
mainPath = pwd;
|
|
flightDataSearchPath = [mainPath];
|
|
[files,flightDataPath] = uigetfile({'*.*'},...
|
|
'Select One or More Files', ...
|
|
'MultiSelect', 'on', ...
|
|
flightDataSearchPath);
|
|
% If only one file is selected, convert char array to cell array
|
|
if ~iscell(files)
|
|
files = cellstr(files);
|
|
numFiles = 1;
|
|
else
|
|
numFiles = size(files,2);
|
|
end
|
|
|
|
% 3) Get file extensions - .BIN oder .mat
|
|
fileExt = cell(numFiles,1);
|
|
for i=1:numFiles
|
|
fileExt{i} = files{i}(end-2:end);
|
|
end
|
|
|
|
% 4) Create cell array with file names
|
|
fileNames = cell(numFiles,1);
|
|
for i=1:numFiles
|
|
fileNames{i} = [flightDataPath files{i}];
|
|
end
|
|
|
|
% 5) Load .mat files - loadedDataConfig is struct, so load fileName field
|
|
chooseSubflights = false;
|
|
for i=1:numFiles
|
|
if strcmp(fileExt{i}, 'mat')
|
|
load(fileNames{i});
|
|
fileNames = loadedDataConfig.fileNames;
|
|
% Fill chooseSubflights variable with false for amount of
|
|
% subflights
|
|
for j=1:length(loadedDataConfig.subflights)
|
|
chooseSubflights(j) = false;
|
|
end
|
|
else
|
|
% Activate subflight choosing if BIN files were selected
|
|
chooseSubflights(i) = true;
|
|
end
|
|
end
|
|
|
|
%% Load complete log data and show tested modes
|
|
|
|
% ---------------------------------------------- %
|
|
% %
|
|
% Loading Dataflash Logs %
|
|
% %
|
|
% ---------------------------------------------- %
|
|
|
|
fprintf('\n-------------------------------------------\nLoading Flash Logs...\n')
|
|
fprintf('-------------------------------------------\n');
|
|
|
|
% Variables to store subflight numbers,amount of subflights and modes
|
|
subflights = 0;
|
|
fileIndices = 0;
|
|
numSubflights = 0;
|
|
modes = 0;
|
|
|
|
for fileIdx = 1:length(fileNames)
|
|
|
|
fprintf('Processing file %d ...\n', fileIdx);
|
|
|
|
% Throw error if files cannot be found
|
|
if ~isfile(fileNames{fileIdx})
|
|
error(['File ' + string(fileNames{fileIdx}) + ' could not be found.']);
|
|
end
|
|
logs(fileIdx) = Ardupilog(char(fileNames{fileIdx}));
|
|
|
|
% Plot and Output for sysid mode
|
|
if any(logs(fileIdx).MODE.Mode == 25)
|
|
fig = figure;
|
|
set(fig, 'defaultAxesColorOrder', [[0 0 0]; [0 0 0]]); % Set axes color to black
|
|
|
|
subplot(2, 1, 1);
|
|
plot(logs(fileIdx).MODE.TimeS, logs(fileIdx).MODE.ModeNum, 'k*');
|
|
ylabel(logs(fileIdx).getLabel('MODE/ModeNum'));
|
|
if ~issorted(logs(fileIdx).MODE.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' MODE.TimeS is not monotonic!'])
|
|
end
|
|
title(['Sysid Mode' fileNames{fileIdx}], 'Interpreter', 'none');
|
|
|
|
% Plot tested SID axes
|
|
hold on;
|
|
yyaxis right;
|
|
plot(logs(fileIdx).SIDS.TimeS, logs(fileIdx).SIDS.Ax, 'r*');
|
|
ylabel(logs(fileIdx).getLabel('SIDS/Ax'));
|
|
if ~issorted(logs(fileIdx).SIDS.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' SIDS.TimeS is not monotonic!'])
|
|
end
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
hold off;
|
|
|
|
% Plot frequency of SID tests
|
|
subplot(2, 1, 2);
|
|
plot(logs(fileIdx).SIDD.TimeS, logs(fileIdx).SIDD.F, 'b.');
|
|
ylabel(logs(fileIdx).getLabel('SIDD/F'));
|
|
if ~issorted(logs(fileIdx).SIDD.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' SIDD.TimeS is not monotonic!'])
|
|
end
|
|
hold on;
|
|
|
|
% Plot subflight numbers
|
|
yyaxis right;
|
|
TimeS = logs(fileIdx).MODE.TimeS;
|
|
subflightNr = 1:length(logs(fileIdx).MODE.Mode);
|
|
plot(TimeS, subflightNr, 'k*');
|
|
ylabel('subflight');
|
|
if ~issorted(logs(fileIdx).SIDS.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' SIDS.TimeS is not monotonic!'])
|
|
end
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
hold off;
|
|
end
|
|
|
|
% Plot for z position controller
|
|
if ~isempty(logs(fileIdx).PSCD.TimeS)
|
|
fig = figure;
|
|
set(fig, 'defaultAxesColorOrder', [[0 0 0]; [0 0 0]]); % Set axes color to black
|
|
|
|
% Plot mode numbers
|
|
subplot(2, 1, 1);
|
|
plot(logs(fileIdx).MODE.TimeS, logs(fileIdx).MODE.ModeNum, 'k*');
|
|
ylabel(logs(fileIdx).getLabel('MODE/ModeNum'));
|
|
if ~issorted(logs(fileIdx).MODE.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' MODE.TimeS is not monotonic!'])
|
|
end
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
ylim([min(logs(fileIdx).MODE.ModeNum)-1 max(logs(fileIdx).MODE.ModeNum)+1]);
|
|
title(['Other Modes' fileNames{fileIdx}], 'Interpreter', 'none');
|
|
|
|
% Plot measured z position
|
|
subplot(2,1,2);
|
|
plot(logs(fileIdx).PSCD.TimeS, -1*(logs(fileIdx).PSCD.PD), 'b.'); % -1 because of inverted logging of PSCD.PD
|
|
ylabel('z-Position (m)');
|
|
if ~issorted(logs(fileIdx).PSCD.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' PSCD.TimeS is not monotonic!'])
|
|
end
|
|
% Plot subflight numbers
|
|
hold on;
|
|
yyaxis right;
|
|
TimeS = logs(fileIdx).MODE.TimeS;
|
|
subflightNr = 1:length(logs(fileIdx).MODE.Mode);
|
|
plot(TimeS, subflightNr, 'k*');
|
|
ylabel('subflight');
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
ylim([0 subflightNr(end)+1]);
|
|
hold off;
|
|
end
|
|
|
|
% Plot for x-y position controller
|
|
if ~isempty(logs(fileIdx).PSCN.TimeS) && ~isempty(logs(fileIdx).PSCE.TimeS)
|
|
fig = figure;
|
|
set(fig, 'defaultAxesColorOrder', [[0 0 0]; [0 0 0]]); % Set axes color to black
|
|
|
|
% Plot mode numbers
|
|
subplot(2, 1, 1);
|
|
plot(logs(fileIdx).MODE.TimeS, logs(fileIdx).MODE.ModeNum, 'k*');
|
|
ylabel(logs(fileIdx).getLabel('MODE/ModeNum'));
|
|
if ~issorted(logs(fileIdx).MODE.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' MODE.TimeS is not monotonic!'])
|
|
end
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
ylim([min(logs(fileIdx).MODE.ModeNum)-1 max(logs(fileIdx).MODE.ModeNum)+1]);
|
|
title(['Other Modes' fileNames{fileIdx}], 'Interpreter', 'none');
|
|
|
|
% Plot measured x and y position
|
|
subplot(2,1,2);
|
|
plot(logs(fileIdx).PSCN.TimeS, logs(fileIdx).PSCN.PN, 'r.');
|
|
ylabel('Position (m)');
|
|
if ~issorted(logs(fileIdx).PSCN.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' PSCN.TimeS is not monotonic!'])
|
|
end
|
|
hold on;
|
|
plot(logs(fileIdx).PSCE.TimeS, logs(fileIdx).PSCE.PE', 'b.');
|
|
if ~issorted(logs(fileIdx).PSCE.TimeS, 'strictascend')
|
|
disp([char(fileNames{fileIdx}) ' PSCE.TimeS is not monotonic!'])
|
|
end
|
|
|
|
% Plot subflight numbers
|
|
yyaxis right;
|
|
TimeS = logs(fileIdx).MODE.TimeS;
|
|
subflightNr = 1:length(logs(fileIdx).MODE.Mode);
|
|
plot(TimeS, subflightNr, 'k*');
|
|
ylabel('subflight');
|
|
xlim([logs(fileIdx).MODE.TimeS(1) logs(fileIdx).PIDR.TimeS(end)]);
|
|
ylim([0 subflightNr(end)+1]);
|
|
hold off;
|
|
legend('x-Position', 'y-Position', 'Subflight') % PSCN is x, PSCE is y
|
|
end
|
|
|
|
% This aids the users configuring the subflights
|
|
disp('Tested Flight Modes:');
|
|
disp(' flight filename');
|
|
disp([' ' num2str(fileIdx) ' ' char(fileNames{fileIdx})]);
|
|
fprintf('Subflight\tMode\tSID_AXIS\n');
|
|
j = 1;
|
|
for i=1:length(logs(fileIdx).MODE.Mode)
|
|
if logs(fileIdx).MODE.Mode(i) == 25
|
|
% If mode is sysid, print tested axis
|
|
|
|
% Decrement sysid mode index j if the start time of
|
|
% sysid mode was not tracked (and does not equal the mode
|
|
% time stamps as a result). If it was not tracked, the
|
|
% settings are identical to the sysid mode before and were
|
|
% not changed.
|
|
if abs(logs(fileIdx).MODE.TimeS(i)-logs(fileIdx).SIDS.TimeS(j)) > 0.01 && j > 1
|
|
j = j-1;
|
|
end
|
|
|
|
fprintf('\t%d\t\t%d\t\t%d\n', i, logs(fileIdx).MODE.Mode(i), logs(fileIdx).SIDS.Ax(j));
|
|
|
|
% Increment sysid mode index j only if the start time was
|
|
% tracked (and equals the mode time stamps as a result).
|
|
% Otherwise, the tracked sysid information belongs to the
|
|
% next sysid flight and is equal to the current, so that
|
|
% the index should not be changed to derive the same infos.
|
|
if abs(logs(fileIdx).MODE.TimeS(i)-logs(fileIdx).SIDS.TimeS(j)) < 0.01
|
|
j = j+1;
|
|
end
|
|
else
|
|
% If not, just print mode
|
|
fprintf('\t%d\t\t%d\t\t-\n', i, logs(fileIdx).MODE.Mode(i));
|
|
end
|
|
end
|
|
|
|
% Let the user select the subflight if BIN files are selected
|
|
iSubflights = 1;
|
|
while(true)
|
|
if chooseSubflights(fileIdx)
|
|
nextSubflight = input('Insert number of subflight to be saved (skip with 0): ');
|
|
% Repeat loop if input is empty
|
|
if isempty(nextSubflight)
|
|
continue;
|
|
end
|
|
% Leave loop when nextSubflight is zero
|
|
if nextSubflight == 0
|
|
fprintf(['Subflight selection for flight ' num2str(fileIdx) ' done.\n']);
|
|
break;
|
|
end
|
|
% Give warnings when given number exceeds number of
|
|
% subflights...
|
|
if nextSubflight > length(logs(fileIdx).MODE.Mode)
|
|
warning('Given number exceeds number of subflights!');
|
|
continue
|
|
end
|
|
% ...or when subflight was selected twice for the same flight
|
|
if any(nextSubflight == subflights(numSubflights+1:end))
|
|
warning('Subflight already selected!');
|
|
continue
|
|
end
|
|
|
|
else
|
|
% Break if number of added subflights equals the total number
|
|
% of loaded subflights for that file (represented by appearence
|
|
% of the respective file index in fileIndices)
|
|
if numel(subflights)-numSubflights >= sum(loadedDataConfig.fileIndices == fileIdx) && subflights ~= 0
|
|
break;
|
|
end
|
|
% mat file was chosen - read subflight from config
|
|
nextSubflight = loadedDataConfig.subflights(numSubflights+iSubflights);
|
|
end
|
|
|
|
% Add subflight, file index and mode
|
|
subflights(numSubflights+iSubflights) = nextSubflight;
|
|
fileIndices(numSubflights+iSubflights) = fileIdx;
|
|
modes(numSubflights+iSubflights) = logs(fileIdx).MODE.Mode(nextSubflight);
|
|
modesConfig(numSubflights+iSubflights) = ModeConfig(modes(end));
|
|
iSubflights = iSubflights + 1; % Update subflight index
|
|
|
|
% Break loop if number of selected subflights equals number of
|
|
% modes
|
|
if length(logs(fileIdx).MODE.Mode) < iSubflights
|
|
break;
|
|
end
|
|
end
|
|
% Update amount of subflights only if 0 was not chosen as subflight
|
|
if ~any(subflights == 0)
|
|
numSubflights = length(subflights);
|
|
end
|
|
end
|
|
clear file ax TimeS subflightNr fig nextSubflight iSubflights numFiles fileIdx fileExt
|
|
clear flightDataSearchPath
|
|
%% Slice logs to desired subflights
|
|
|
|
% Time increment that is used to extend the slicing time interval to
|
|
% prevent signals (that are only defined at the first time step) to be
|
|
% excluded from the sliced log because of floating point precision
|
|
dt_ext = 0.001;
|
|
|
|
% Create list for axis that were tested (axis=0 if sysid was not used)
|
|
axisList = 0;
|
|
|
|
% ---------------------------------------------- %
|
|
% %
|
|
% Extract relevant log messages and slice logs %
|
|
% %
|
|
% ---------------------------------------------- %
|
|
|
|
% slice the system identification flight subsection(s)
|
|
fprintf('\n-------------------------------------------\n')
|
|
fprintf('Extracting messages and slicing logs...\n');
|
|
fprintf('-------------------------------------------\n');
|
|
% Delete sid in case of reloading for new config
|
|
clear sid
|
|
for i = 1:length(fileIndices)
|
|
fprintf(['Subflight Nr. ' num2str(i) '\n']);
|
|
% Check if current subflight number doesn't exceed actual subflights
|
|
if (subflights(i) > length(logs(fileIndices(i)).MODE.Mode) )
|
|
error(['Defined subflight (index ' num2str(i) ') exceeds amount of actual subflights!']);
|
|
end
|
|
|
|
% Filter log messages
|
|
logFilterMsgs = {};
|
|
logFilterMsgs = modesConfig(i).filter_msgs;
|
|
% Check if log_filter_msgs is not empty
|
|
if ~iscellstr(logFilterMsgs)
|
|
error('logFilterMsgs variable is not a string cell array');
|
|
end
|
|
% Check if message is included in flight log
|
|
for msg = logFilterMsgs
|
|
% Skip message if it was not defined - delete it from array
|
|
if ~isprop(logs(fileIndices(i)), msg{1})
|
|
warning(['Message ' msg{1} ' not included in subflight ' ...
|
|
num2str(subflights(i)) ' of logfile ' ...
|
|
fileNames{fileIndices(i)} '!'])
|
|
elseif isempty(logs(fileIndices(i)).(msg{1}).LineNo)
|
|
warning(['Message ' msg{1} ' not included in subflight ' ...
|
|
num2str(subflights(i)) ' of logfile ' ...
|
|
fileNames{fileIndices(i)} '!']);
|
|
logFilterMsgs(strcmp(logFilterMsgs, msg{1})) = [];
|
|
end
|
|
end
|
|
log = logs(fileIndices(i)).filterMsgs(logFilterMsgs);
|
|
|
|
% Slice log to desired time section
|
|
if subflights(i) < length(logs(fileIndices(i)).MODE.Mode)
|
|
% Current subflight is not last subflight of log
|
|
start_time = logs(fileIndices(i)).MODE.TimeUS(subflights(i))/1e6;
|
|
% end time: Subtract 2*dt_ext to prevent first time step of
|
|
% next flight mode to be part of sliced log
|
|
end_time = logs(fileIndices(i)).MODE.TimeUS(subflights(i)+1)/1e6-2*dt_ext;
|
|
else % use end time of TimeUS message in case of the last subflight
|
|
start_time = logs(fileIndices(i)).MODE.TimeUS(subflights(i))/1e6;
|
|
end_time = logs(fileIndices(i)).RATE.TimeUS(end)/1e6;
|
|
end
|
|
log = log.getSlice( [start_time-dt_ext end_time+dt_ext], 'TimeS');
|
|
|
|
% Check if flight mode of log corresponds to defined mode in config
|
|
if modes(i) ~= log.MODE.Mode
|
|
error_msg = ['Defined Mode (' num2str(modes(i)) ') in ' ...
|
|
'mode_list with index ' num2str(i) ' does not correspond '...
|
|
'to mode of log (' num2str(log.MODE.Mode) ')!'];
|
|
error(error_msg);
|
|
end
|
|
|
|
% preserve some messages that are outside of the slice time interval
|
|
log.FMT = logs(fileIndices(i)).FMT;
|
|
log.UNIT = logs(fileIndices(i)).UNIT;
|
|
log.FMTU = logs(fileIndices(i)).FMTU;
|
|
log.MULT = logs(fileIndices(i)).MULT;
|
|
log.PARM = logs(fileIndices(i)).PARM;
|
|
% Update the number of actual included messages
|
|
log.numMsgs = 0;
|
|
for msg = log.msgsContained
|
|
msgName = char(msg);
|
|
% Check if message has been sliced off log
|
|
if ~isvalid(log.(msgName))
|
|
warning(['Message ' msg{1} ' in log ' ...
|
|
num2str(i) ' was sliced off message!']);
|
|
else
|
|
log.numMsgs = log.numMsgs + length(log.(msgName).LineNo);
|
|
end
|
|
end
|
|
|
|
% Get axis from flight. Set to 0 if sysid mode is not active.
|
|
axis = get_axis(log);
|
|
axis = axis(1);
|
|
% Add new axis.
|
|
axisList(i) = axis;
|
|
|
|
% Resample log and save it to sid array
|
|
sidLogs(numOldSubflights + i).data = resampleLog(log, true, 'linear');
|
|
end
|
|
|
|
% Print modes and/or tested sid axis in log array
|
|
fprintf('\nChosen test flights:\n')
|
|
fprintf('Dataset\t|File\t|Subflight\t|Mode\n')
|
|
for i=1:length(sidLogs)-numOldSubflights
|
|
fprintf('sid(%d)\t|%d\t\t|%d\t\t\t|%d\n', int16(i+numOldSubflights), ...
|
|
fileIndices(i), subflights(i), sidLogs(numOldSubflights + i).data.MODE.Mode(1));
|
|
end
|
|
|
|
% Save configuration of loaded data to struct and to repo
|
|
if numOldSubflights == 0
|
|
% No configuration in workspace yet
|
|
loadedDataConfig.fileNames = fileNames;
|
|
loadedDataConfig.fileIndices = fileIndices;
|
|
loadedDataConfig.subflights = subflights;
|
|
loadedDataConfig.modes = modes;
|
|
loadedDataConfig.modesConfig = modesConfig;
|
|
loadedDataConfig.axisList = axisList;
|
|
else
|
|
% Add new configuration to existing configuration array
|
|
for i=1:length(fileNames)
|
|
loadedDataConfig.fileNames{end+1} = fileNames{i};
|
|
end
|
|
for i=1:length(fileIndices)
|
|
% File indices must be adjusted to index of file in existing config
|
|
loadedDataConfig.fileIndices(numOldSubflights+i) = length(loadedDataConfig.fileNames);
|
|
loadedDataConfig.subflights(numOldSubflights+i) = subflights(i);
|
|
loadedDataConfig.modes(numOldSubflights+i) = modes(i);
|
|
loadedDataConfig.modesConfig(numOldSubflights+i) = modesConfig(i);
|
|
loadedDataConfig.axisList(numOldSubflights+i) = axisList(i);
|
|
end
|
|
end
|
|
loadedDataConfigFile = [flightDataPath 'dataLoadConfig.mat'];
|
|
save(loadedDataConfigFile, 'loadedDataConfig');
|
|
|
|
% Save new flightdata to repo
|
|
newDataFile = 'sid.mat';
|
|
newDataFile = [flightDataPath newDataFile];
|
|
if exist(newDataFile, 'file')
|
|
[filename, path] = uiputfile({'*.mat','Mat file (*.mat)';'*.*','All files (*.*)'}, 'Save File Name', ...
|
|
newDataFile);
|
|
% Set user-specified file name
|
|
newDataFile = [flightDataPath filename];
|
|
end
|
|
if newDataFile ~= 0
|
|
if exist('idData','var')
|
|
save(newDataFile, 'sidLogs', 'idData', 'loadedDataConfig');
|
|
else
|
|
save(newDataFile, 'sidLogs', 'loadedDataConfig');
|
|
end
|
|
end
|
|
|
|
clear msg msgName filename path in_dat out_dat len idd delta_T k
|
|
clear start_time end_time sid idDataObj minNumElem
|
|
clear newDataFile logPathName numOldSubflights numOldSysidflights
|
|
clear fmin fminSweep fmax fmaxSweep addData logs
|
|
|
|
clear i j dt_ext log_filenames axis axisList files chooseSubflights
|
|
clear numSubflights storedFile logFilterMsgs fileNames fileIndices subflights
|
|
clear dataLoadConfigFile dataStored loadedDataConfigFile modes modesConfig
|
|
|
|
% Interact with user to continue
|
|
uiwait(msgbox(['Data loaded. Press ok to close figures and continue.'],'Data loaded','none','nonmodal'));
|
|
close all
|
|
%% Functions
|
|
% Function to read tested axis from log
|
|
function axis = get_axis(obj)
|
|
if isprop(obj, 'SIDS')
|
|
axis = obj.SIDS.Ax;
|
|
else
|
|
% Set to zero if sysid mode is not active
|
|
axis = 0;
|
|
end
|
|
end |