diff --git a/VERSION b/VERSION
index 0947c3313659827e4a73ff591c1fdcbccf9ed12f..66321c084ca9a642e8b308c01850cfd0351b48d1 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-188
\ No newline at end of file
+189
\ No newline at end of file
diff --git a/lib/+artoa/+controller/+file/+profile2rfb/close.m b/lib/+artoa/+controller/+file/+profile2rfb/close.m
new file mode 100644
index 0000000000000000000000000000000000000000..945ffe8ae3aae6f0652066a59b978fc55226bbdb
--- /dev/null
+++ b/lib/+artoa/+controller/+file/+profile2rfb/close.m
@@ -0,0 +1,27 @@
+function [ ] = close(pSource, ~)
+%CLOSE Cleans up all variables that are used by the pSource GUI.
+%   Changes to the data is not being modified.
+
+global artoaGui;
+
+%% Setup variables
+figureName = 'convertProfilesToRfb';
+
+%% Close the figure
+if nargin > 1 && isvalid(pSource)
+    delete(pSource);
+elseif isfield(artoaGui.figures, figureName) && isvalid(artoaGui.figures.(figureName))
+    delete(artoaGui.figures.(figureName));
+end
+
+%% Clean up variables
+
+if artoa.data.hasMember(artoaGui, {figureName})
+    artoaGui = rmfield(artoaGui, figureName);
+end
+
+if artoa.data.hasMember(artoaGui, {'figures', figureName})
+    artoaGui.figures = rmfield(artoaGui.figures, figureName);
+end
+
+end
\ No newline at end of file
diff --git a/lib/+artoa/+controller/+file/+profile2rfb/open.m b/lib/+artoa/+controller/+file/+profile2rfb/open.m
new file mode 100644
index 0000000000000000000000000000000000000000..77a59bca305da06ba32dff680710ab2201c40ca2
--- /dev/null
+++ b/lib/+artoa/+controller/+file/+profile2rfb/open.m
@@ -0,0 +1,18 @@
+function [] = open(~, ~)
+%OPEN Summary of this function goes here
+%   Detailed explanation goes here
+
+global artoaGui;
+
+%% Check if the gui is already opened
+
+if isfield(artoaGui.figures, 'convertProfilesToRfb')
+    figure(artoaGui.figures.convertProfilesToRfb);
+    return
+end
+
+%% Open the gui
+artoa.gui.file.convertProfilesToRfb();
+
+end
+
diff --git a/lib/+artoa/+controller/+main/open.m b/lib/+artoa/+controller/+main/open.m
index 3410aa51e97d0d7bc6bef6bd914fba32a3f86afd..881d1d0db44ce5345f7c9b4d9891a60e3b169f51 100644
--- a/lib/+artoa/+controller/+main/open.m
+++ b/lib/+artoa/+controller/+main/open.m
@@ -30,6 +30,7 @@ callbacks.openEditOffsets = @artoa.controller.edit.offsets.open;
 callbacks.openTrackParameter = @artoa.controller.track.parameter.open;
 callbacks.openTrackTrajectoryOutput = @artoa.controller.track.trajectoryOutput.open;
 callbacks.switchHideDeletedDataPoints = @artoa.controller.switchHideDeletedPoints;
+callbacks.convertProfilesToRfb = @artoa.controller.file.profile2rfb.open;
 
 %% Prepare callbacks for track parameter
 callbacks.checkboxDopplerCorrection = @artoa.controller.track.parameter.checkboxDopplerCorrection;
diff --git a/lib/+artoa/+gui/+file/convertProfilesToRfb.m b/lib/+artoa/+gui/+file/convertProfilesToRfb.m
new file mode 100644
index 0000000000000000000000000000000000000000..27837048c5ca6d1e51e1685f2c6193305d742127
--- /dev/null
+++ b/lib/+artoa/+gui/+file/convertProfilesToRfb.m
@@ -0,0 +1,202 @@
+function [] = convertProfilesToRfb()
+%CONVERTPROFILESTORFB Defines the profile to rfb conversion tool.
+
+global artoaGui;
+
+%% Initialize required variables
+
+windowTitle = 'ARTOA4 - Profile to RFB converter';
+
+
+%% Initialize gui
+
+artoaGui.figures.convertProfilesToRfb = figure( ...
+    'Name', windowTitle, ...
+    'NumberTitle', 'off' ...
+);
+
+
+artoaGui.convertProfilesToRfb = struct();
+
+set( ...
+    artoaGui.figures.convertProfilesToRfb, ...
+    'CloseRequestFcn', ...
+    @artoa.controller.file.profile2rfb.close, ...
+    'Units', 'normalized' ...
+);
+
+%% Generate Controls
+
+left = .05;
+width = .9;
+
+%% Source frame
+artoaGui.convertProfilesToRfb.frameSource = uipanel( ...
+    'Parent', artoaGui.figures.convertProfilesToRfb, ...
+    'Title', 'Source folder (*.profiles)', ...
+    'Units', 'normalized', ...
+    'BackgroundColor', 'white', ...
+    'Position', [left .65 width .3] ...
+);
+
+artoaGui.convertProfilesToRfb.buttonSelectFolder = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameSource, ...
+    'String', 'Pick a folder', ...
+    'Style', 'PushButton', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [.7 .5 .25 .3], ...
+    'Callback', @selectSourceFolder ...
+);
+
+artoaGui.convertProfilesToRfb.textSourceFolder = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameSource, ...
+    'Style', 'edit', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Enable', 'off', ...
+    'Position', [.05 .5 .6 .3] ...
+);
+
+artoaGui.convertProfilesToRfb.buttonSelectMetadata = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameSource, ...
+    'String', 'Pick metadata file', ...
+    'Style', 'PushButton', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [.7 .1 .25 .3], ...
+    'Callback', @selectSourceMetadata ...
+);
+
+artoaGui.convertProfilesToRfb.textSourceMetadata = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameSource, ...
+    'Style', 'edit', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Enable', 'off', ...
+    'Position', [.05 .1 .6 .3] ...
+);
+
+%% Target frame
+artoaGui.convertProfilesToRfb.frameTarget = uipanel( ...
+    'Parent', artoaGui.figures.convertProfilesToRfb, ...
+    'Title', 'Target file (*.rfb)', ...
+    'Units', 'normalized', ...
+    'BackgroundColor', 'white', ...
+    'Position', [left .4 width .2] ...
+);
+
+artoaGui.convertProfilesToRfb.buttonSelectTargetFile = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameTarget, ...
+    'String', 'Select a file', ...
+    'Style', 'PushButton', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [.7 .4 .25 .4], ...
+    'Callback', @selectTargetFile ...
+);
+
+artoaGui.convertProfilesToRfb.textTargetFile = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameTarget, ...
+    'Style', 'edit', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Enable', 'off', ...
+    'Position', [.05 .4 .6 .4] ...
+);
+
+%% Execute frame
+artoaGui.convertProfilesToRfb.frameExecute = uipanel( ...
+    'Parent', artoaGui.figures.convertProfilesToRfb, ...
+    'Title', 'Convert', ...
+    'Units', 'normalized', ...
+    'BackgroundColor', 'white', ...
+    'Position', [left .1 width .2] ...
+);
+
+artoaGui.convertProfilesToRfb.textStatus = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameExecute, ...
+    'String', 'Please input source and target...', ...
+    'Style', 'Text', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [.1 .4 .35 .4] ...
+);
+
+artoaGui.convertProfilesToRfb.buttonExecuteConversion = uicontrol( ...
+    'Parent', artoaGui.convertProfilesToRfb.frameExecute, ...
+    'String', 'Start conversion', ...
+    'Style', 'PushButton', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Enable', 'off', ...
+    'Position', [.5 .4 .3 .4], ...
+    'Callback', @convert ...
+);
+
+%% Helper functions
+
+    function selectSourceFolder(~, ~)
+        dirname = uigetdir(pwd, 'Select source folder');
+        if (dirname == 0)
+            return
+        end
+        artoaGui.convertProfilesToRfb.textSourceFolder.String = dirname;
+        checkInputs();
+    end
+
+    function selectSourceMetadata(~, ~)
+        [filename, path] = uigetfile('*.xml', 'Select metadata file');
+        if (filename == 0)
+            return
+        end
+        artoaGui.convertProfilesToRfb.textSourceMetadata.String = ...
+            fullfile(path, filename);
+        checkInputs();
+    end
+
+    function selectTargetFile(~, ~)
+        [filename, path] = uiputfile('*.rfb', 'Select target file');
+        if (filename == 0)
+            return
+        end
+        artoaGui.convertProfilesToRfb.textTargetFile.String = ...
+            fullfile(path, filename);
+        checkInputs();
+    end
+
+    function checkInputs(~, ~)
+        if all(strcmp({ ...
+                artoaGui.convertProfilesToRfb.textSourceFolder.String, ...
+                artoaGui.convertProfilesToRfb.textTargetFile.String, ...
+                artoaGui.convertProfilesToRfb.textSourceMetadata.String ...
+                }, '') == 0)
+            artoaGui.convertProfilesToRfb.buttonExecuteConversion.Enable = 'on';
+            setStatus('Ready for conversion!');
+        else
+            artoaGui.convertProfilesToRfb.buttonExecuteConversion.Enable = 'off';
+            setStatus('Please input source and target...');
+        end
+    end
+
+    function setStatus(pStatusMessage)
+        artoaGui.convertProfilesToRfb.textStatus.String = pStatusMessage;
+    end
+
+    function convert(~, ~)
+        setStatus('Loading files...');
+        metadata = artoaGui.convertProfilesToRfb.textSourceMetadata.String;
+        inputFolder = artoaGui.convertProfilesToRfb.textSourceFolder.String;
+        target = artoaGui.convertProfilesToRfb.textTargetFile.String;
+        float = nemo.load.single_float(inputFolder, filesep);
+        setStatus('Creating rfb structure...');
+        [rfb, ~] = nemo.rfb.create(float, nemo.load.float_metadata(metadata));
+        setStatus('Save to target...');
+        if ~nemo.rfb.save(rfb, target, true)
+            setStatus('Something failed during conversion!');
+        end
+        setStatus('...success!');
+    end
+
+end
+
diff --git a/lib/+artoa/+gui/main.m b/lib/+artoa/+gui/main.m
index 2a3c4d9f3555ba7b4f6ec5780eb6bf17a1000ff8..3168510b35b05cc78abe9ba05339ce822e739de6 100644
--- a/lib/+artoa/+gui/main.m
+++ b/lib/+artoa/+gui/main.m
@@ -15,6 +15,7 @@ availableCallbacks = { ...
     'saveInterim', ...
     'saveOptimumTables', ...
     'loadArtoaIni', ...
+    'convertProfilesToRfb', ...
     'quit', ...
     'openEditTemperature', ...
     'openEditPressure', ...
@@ -135,6 +136,20 @@ uimenu( ...
     'Callback', pCallbacks.saveOptimumTables ...
 );
 
+% CONVERT
+artoaGui.main.menus.fileConvert = uimenu( ...
+    artoaGui.main.menus.file, ...
+    'Label', 'Convert', ...
+    'Enable', 'on' ...
+);
+
+uimenu( ...
+    artoaGui.main.menus.fileConvert, ...
+    'Label', '*.profile to .rfb', ...
+    'Callback', pCallbacks.convertProfilesToRfb ...
+);
+
+% RELOAD ARTOA INI
 artoaGui.main.menus.fileReloadArtoaIni = uimenu( ...
     artoaGui.main.menus.file, ...
     'Label', 'Reload artoa.ini', ...
diff --git a/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m
new file mode 100644
index 0000000000000000000000000000000000000000..4bb30a8d6bc8c296e1ca9c3b400f9934c38f55bf
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_csv_table.m
@@ -0,0 +1,127 @@
+function [ boolean ] = create_csv_table( p_metadata, p_config )
+%CREATE_CSV_TABLE Creates a csv file containing table with information of all floats.
+%
+%   Parameters:
+%   p_metadata:        Struct containing all floats (load with nemo.load.data)
+%   p_config:          Struct containing global configuration file (load with
+%                      nemo.load.config()).
+%
+%   Returns:
+%   boolean:  False if something went wrong during the creation of the table.
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+
+if (~iscell(p_metadata) || ~isstruct(p_config))
+    disp([mfilename ': Wrong parameter. Please check your input!']);
+    return
+end
+
+%% Collect all required information
+
+serial = [];
+imei = [];
+wmoid = [];
+deployment_datetime = [];
+deployment_lat = [];
+deployment_lon = [];
+profile_count = [];
+latest_transmission = [];
+
+float_count = length(p_metadata);
+
+for o_float = 1:float_count % collect all required information of every float
+    % load the latest float
+    [current_profile, current_profile_count] = nemo.load.latest_profile( ...
+        p_config, ...
+        p_metadata{o_float}.GENERAL_INFO.SERIAL ...
+    );
+
+    % get all numbers of nemo floats
+    serial(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.SERIAL;
+    imei(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.IMEI;
+    wmoid(end+1, 1) = p_metadata{o_float}.GENERAL_INFO.WMO_ID;
+    % deploment info
+	deployment_datetime(end+1, :) = datevec( ...
+        [ ...
+            p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_DATE ...
+            p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_TIME ...
+        ], 'dd.mm.yyyyHH:MM');
+
+
+%
+%     [ ...
+%         p_metadata{o_float}.DEPLOYMENT_INFO.deployment_date(3), ...
+%         current_profile.DEPLOYMENT_INFO.deployment_date(2), ...
+%         current_profile.DEPLOYMENT_INFO.deployment_date(1), ...
+%         current_profile.DEPLOYMENT_INFO.deployment_time, ...
+%         0];
+    deployment_lat(end+1, 1) = p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_LAT;
+    deployment_lon(end+1, 1) = p_metadata{o_float}.DEPLOYMENT_INFO.DEPLOYMENT_LON;
+    % number of received profiles
+	profile_count(end+1, 1) = current_profile_count;
+	% last float transmittion
+    if ~isempty(current_profile) && ~isempty(current_profile.IRIDIUM_DATA)
+        latest_transmission(end+1, :) = current_profile.IRIDIUM_DATA(4:9);
+    else
+        latest_transmission(end+1, :) = nan(1, 6);
+    end
+
+end
+
+% create filename
+filename = fullfile( ...
+    p_config.ANALYSIS_RESULTS.output_path, ...
+    p_config.ANALYSIS_RESULTS.compact_info_filename ...
+    );
+
+% create folder if necessary
+if ~exist(p_config.ANALYSIS_RESULTS.output_path, 'dir')
+    mkdir(p_config.ANALYSIS_RESULTS.output_path);
+end
+
+% create header of table
+table_header = { ...
+    'serial', 'imei', 'wmo_id', 'deployment_time', 'deployment_lat', 'deployment_lon', ...
+    'latest_transmission', 'profile_count' ...
+};
+
+% create matlab table with the collected information
+compact_info = table(serial, imei, wmoid, datetime(deployment_datetime, 'Format', 'yyyy-MM-dd HH:m'), deployment_lat, deployment_lon, ...
+    datetime(latest_transmission, 'Format', 'yyyy-MM-dd HH:mm'), profile_count, 'VariableNames', table_header);
+
+% write data to file
+writetable(compact_info, filename, 'Delimiter', ',');
+try
+% copy template file to csv folder
+copyfile(p_config.ANALYSIS_RESULTS.compact_info_html_template, [ ...
+    p_config.ANALYSIS_RESULTS.output_path ...
+    'index.html' ...
+]);
+% update its timestamp via java
+import java.io.File java.text.SimpleDateFormat
+f = File([ p_config.ANALYSIS_RESULTS.output_path 'index.html' ]);
+sdf = SimpleDateFormat('HH:mm dd/MM/yyyy');
+today = datetime('now');
+newDate = sdf.parse([ ...
+    int2str(today.Hour) ':' int2str(today.Minute) ' ' ...
+    int2str(today.Day) '/' int2str(today.Month) '/' int2str(today.Year) ...
+]);
+f.setLastModified(newDate.getTime);
+clear f;
+% copy javascript folder
+copyfile(p_config.ANALYSIS_RESULTS.input_javascript_folder, ...
+    [p_config.ANALYSIS_RESULTS.output_path 'js' p_config.GENERAL.folder_separator] ...
+);
+catch ex
+warning('Could not copy templates or JavaScript folder. If you experience Problems try deleting those files/folders and rerun!');
+boolean = false;
+return;
+end
+
+% everything went fine! :)
+boolean = true;
+
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m
new file mode 100644
index 0000000000000000000000000000000000000000..af40e3743eba8e6a0a53f9b04f34dc2a108248bf
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+analysis/create_html_summaries.m
@@ -0,0 +1,197 @@
+function [ boolean ] = create_html_summaries( p_nemo_data, p_config )
+%CREATE_HTML_SUMMARY Generates a summary of every float given in the nemo data.
+%   Uses the nemo configuration for linking the html summaries to the
+%   overview table generated by create_csv_table
+%
+%   Parameters:
+%   p_nemo_data:            The nemo data where the summaries will be
+%                           generated from.
+%   p_config:               The nemo configuration
+%
+%   Returns:
+%   boolean:                Always true.
+
+path = fullfile(p_config.ANALYSIS_RESULTS.output_path, 'html');
+
+if (~exist(path, 'dir'))
+    mkdir(path);
+end
+
+for i = 1:length(p_nemo_data)
+
+    f = p_nemo_data{i};
+    float_number = f{1}.FLOAT_IDENTIFICATION.float_serial_number;
+    html = ['<!DOCTYPE html><head><title>Summary of Float ' ...
+             num2str(float_number) ...
+            '</title><style>' ...
+            'body, a{font-family: Arial;color: #4B4B4D;}' ...
+            'th{line-height: 60px;background: #00ACE5;color: #003E6E;font-size: 30px;}' ...
+            'td{line-height: 40px;border: thin solid black; padding-left: 5px;}' ...
+            '.value{width: 70%;}' ...
+            '.name{width: 30%;background: #BCBDBF;font-weight: bold;}' ...
+            'table{border-collapse: collapse;border: thin solid black;width: 100%;}' ...
+            'img{width: 100%;font-weight: bold;font-size: 30px;}' ...
+            '.overlay{z-index: 2;}' ...
+            '#show-floattable{padding:20px;background: #00ACE5;color: #003E6E;font-size: 18px;position: fixed;right: 20px;top: 10px;cursor: pointer;}' ...
+            '</style>' ...
+            '</head><body>' ...
+            '<a id="show-floattable" class="overlay" onclick="(function(){history.back();})()">Show all floats</a>' ...
+            '<table>'];
+
+
+    %% Add latest update time
+    html = [ html '<tr><td class="name">Updated on:</td><td class="value">' datestr(datetime('now', 'TimeZone', 'Europe/London')) '</td></tr>'];
+
+
+    %% Add float identification table
+    html = [ html '<th colspan="2">FLOAT_IDENTIFICATION</th>' ];
+
+    fnames = fieldnames(f{1}.FLOAT_IDENTIFICATION);
+    for j = 1:length(fnames)
+
+        fieldvalue = f{1}.FLOAT_IDENTIFICATION.(fnames{j});
+
+        switch(class(fieldvalue))
+            case {'double', 'logical'}
+                fieldvalue = num2str(fieldvalue);
+        end
+        html = [ html ...
+                 '<tr><td class="name">' fnames{j} ...
+                 '</td><td class="value">' fieldvalue ...
+                 '</td></tr>' ...
+               ];
+    end
+
+    %% Add float deployment table
+    html = [ html '<th colspan="2">DEPLOYMENT_INFO</th>' ];
+
+    fnames = fieldnames(f{1}.DEPLOYMENT_INFO);
+    for j = 1:length(fnames)
+
+        fieldvalue = f{1}.DEPLOYMENT_INFO.(fnames{j});
+
+        switch(class(fieldvalue))
+            case {'double', 'logical'}
+                fieldvalue = num2str(fieldvalue);
+        end
+        html = [ html ...
+                 '<tr><td class="name">' fnames{j} ...
+                 '</td><td class="value">' fieldvalue ...
+                 '</td></tr>' ...
+               ];
+    end
+
+    %% Add float deployment table
+    html = [ html '<th colspan="2">OVERALL_MISSION_INFORMATION</th>' ];
+
+    fnames = fieldnames(f{1}.OVERALL_MISSION_INFORMATION);
+    for j = 1:length(fnames)
+
+        fieldvalue = f{1}.OVERALL_MISSION_INFORMATION.(fnames{j});
+
+        switch(class(fieldvalue))
+            case {'double', 'logical'}
+                fieldvalue = num2str(fieldvalue);
+        end
+        html = [ html ...
+                 '<tr><td class="name">' fnames{j} ...
+                 '</td><td class="value">' fieldvalue ...
+                 '</td></tr>' ...
+               ];
+    end
+
+    %% Add plots
+
+    html = [ html '<th colspan="2">PLOTS</th>' ];
+
+    html = [ html '<tr><td class="name">Arrival of profiles</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_fig ...
+             p_config.PLOT_ARRIVAL_OF_PROFILES.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_image ...
+             p_config.PLOT_ARRIVAL_OF_PROFILES.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">Ascent</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_ASCENT.storage_directory_fig ...
+             p_config.PLOT_ASCENT.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_ASCENT.storage_directory_image ...
+             p_config.PLOT_ASCENT.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">Categorized timings</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_fig ...
+             p_config.PLOT_CATEGORIZED_TIMINGS.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_image ...
+             p_config.PLOT_CATEGORIZED_TIMINGS.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+     html = [ html '<tr><td class="name">Timing information</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_TIMING_INFORMATION.storage_directory_fig ...
+             p_config.PLOT_TIMING_INFORMATION.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_TIMING_INFORMATION.storage_directory_image ...
+             p_config.PLOT_TIMING_INFORMATION.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">RAFOS</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_RAFOS.storage_directory_fig ...
+             p_config.PLOT_RAFOS.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_RAFOS.storage_directory_image ...
+             p_config.PLOT_RAFOS.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">Single floats map</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_fig ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_image ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">Temperature, Salinity</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_TEMP_SAL.storage_directory_fig ...
+             p_config.PLOT_TEMP_SAL.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_TEMP_SAL.storage_directory_image ...
+             p_config.PLOT_TEMP_SAL.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+    html = [ html '<tr><td class="name">Temperature, Salinity, Potential Temperature</td>' ...
+             '<td class="value"><a href="../../plots/' ...
+             p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_fig ...
+             p_config.PLOT_TEMP_SAL_PTEMP.figure_filename ...
+             num2str(float_number) '.fig">' ...
+             '<img alt="No image available!" src="../../plots/' ...
+             p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_image ...
+             p_config.PLOT_TEMP_SAL_PTEMP.image_filename ...
+             num2str(float_number) '.png"></a>' ...
+             '</td></tr>'];
+
+    html = [ html '</table></body></html>' ];
+
+    %% Write html file
+
+    fid = fopen(fullfile(path, [num2str(float_number) '.html']), 'w');
+    fwrite(fid, html);
+    fclose(fid);
+
+end
+
+boolean = true;
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..408bf574c7f2d07020aa201a68f6d09bfea9dce0
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+convert/combined_float2nemostruct.m
@@ -0,0 +1,970 @@
+function [ nemo_float ] = combined_float2nemostruct( p_combined_float )
+%FLOAT Converts the given float in optimare structure to the structre of AWI.
+%   This is only a temporary function and will be deprecated as soon as the
+%   decoding process has been rewritten. During the decoding, optimare created a
+%   variable structure that differs from the one AWI is using. This function
+%   transfers all variables into the AWI format. In addition to that, it also
+%   checks for incomplete profiles and always saves the profile with the biggest
+%   size of CTD data.
+%
+%   Parameters:
+%   p_float:     Structure that was created during the decoding process.
+%
+%   Returns:
+%   nemo_float:  Structure that is being used by AWI. False
+%                if there is no data, no profiles or if something went wrong
+%                during conversion.
+
+%% Initialize return variables
+nemo_float = {};
+
+%% Convert information to AWI structure
+
+if ~isfield(p_combined_float, 'DECODED_SBD')
+    % no data available
+    nemo_float = false;
+    return
+end
+% get fieldnames of every decoded profile
+profile_fieldnames = fieldnames(p_combined_float.DECODED_SBD);
+
+% sort iridium messages by profile number
+[profile_numbers_sorted, sorted_indices] = sort_iridium_messages(profile_fieldnames);
+
+if ~sorted_indices
+    % something went wrong, for example only incomplete iridium messages available
+    nemo_float = false;
+    return
+end
+
+% get profiles with most CTD data if multiple ones have been transmitted
+best_ctd_indices = filter_multiple_profiles(sorted_indices);
+
+% count valid profiles
+valid_profile_count = length(best_ctd_indices);
+
+
+%% Get format data that is always the same
+deployment_info_struct = deployment_info();
+rafos_values_format_struct = rafos_values_format();
+profile_header_struct = profile_header();
+quality_control_header_struct = qualtiy_control_header();
+profile_data_header_struct = profile_data_header();
+surface_gps_data_format_struct = surface_gps_data_format();
+iridium_positions_format_struct = iridium_positions_format();
+iridium_data_format_struct = iridium_data_format();
+
+% create variable, counting available valid profiles
+% differs to valid_profile_count because only "Profile Message" is being accepted
+valid_profiles = 0;
+
+for o_profile = 1:valid_profile_count
+    % get index of current profile, may not be same as o_profile because optimare
+	% structure is not sorted by profile numbers
+    current_index = best_ctd_indices(o_profile);
+	
+    if ~strcmp( ...
+               p_combined_float.DECODED_SBD.(profile_fieldnames{current_index}).DATATYPE, ...
+               'Profile Message' ...
+              )
+			% everything that is not a profile or a test message is being ignored
+            continue
+    else
+		% increase number of valid profiles
+        valid_profiles = valid_profiles + 1;
+    end
+    try % converting the input structure
+	
+        %% FLOAT_IDENTIFICATION
+        nemo_float{1, valid_profiles}.FLOAT_IDENTIFICATION = ...
+            float_identification(profile_fieldnames{current_index});
+        %% OVERALL_MISSION_INFORMATION
+        nemo_float{1, valid_profiles}.OVERALL_MISSION_INFORMATION = ...
+            overall_mission_information(profile_fieldnames{current_index});
+        %% DEPLOYMENT_INFO
+        nemo_float{1, valid_profiles}.DEPLOYMENT_INFO = ...
+            deployment_info_struct;
+        %% PROFILE_TECHNICAL_DATA
+        nemo_float{1, valid_profiles}.PROFILE_TECHNICAL_DATA = ...
+            profile_technical_data(profile_fieldnames{current_index});
+        %% BOTTOM_VALUES_DURING_DRIFT
+        nemo_float{1, valid_profiles}.BOTTOM_VALUES_DURING_DRIFT = ...
+            bottom_values_during_drift(profile_fieldnames{current_index});
+        %% RAFOS_VALUES_FORMAT
+        nemo_float{1, valid_profiles}.RAFOS_VALUES_FORMAT = ...
+            rafos_values_format_struct;
+        %% RAFOS_VALUES
+        nemo_float{1, valid_profiles}.RAFOS_VALUES = ...
+            rafos_values(profile_fieldnames{current_index});
+        %% PROFILE_HEADER
+        nemo_float{1, valid_profiles}.PROFILE_HEADER = ...
+            profile_header_struct;
+        %% QUALITY_CONTROL_HEADER
+        nemo_float{1, valid_profiles}.QUALITY_CONTROL_HEADER = ...
+            quality_control_header_struct;
+        %% PROFILE_DATA_HEADER
+        nemo_float{1, valid_profiles}.PROFILE_DATA_HEADER = ...
+            profile_data_header_struct;
+        %% PROFILE_DATA
+        nemo_float{1, valid_profiles}.PROFILE_DATA = ...
+            profile_data(profile_fieldnames{current_index});
+			
+        % if there is no profile data, clean up and go to next profile
+        if isempty(nemo_float{1, valid_profiles}.PROFILE_DATA)
+            % skip profiles without data
+            nemo_float{1, valid_profiles} = [];
+            valid_profiles = valid_profiles - 1;
+            continue
+        end
+		
+        %% SURFACE_GPS_DATA_FORMAT
+        nemo_float{1, valid_profiles}.SURFACE_GPS_DATA_FORMAT = ...
+            surface_gps_data_format_struct;
+        %% SURFACE_GPS_DATA
+        nemo_float{1, valid_profiles}.SURFACE_GPS_DATA = ...
+            surface_gps_data(profile_fieldnames{current_index});
+        %% IRIDIUM_POSITIONS_FORMAT
+        nemo_float{1, valid_profiles}.IRIDIUM_POSITIONS_FORMAT = ...
+            iridium_positions_format_struct;
+        %% IRIDIUM_POSITIONS & IRIDIUM_DATA
+        [ iridium_positions, iridium_best ] = ...
+            iridium_positions_and_data(profile_fieldnames{current_index});
+        nemo_float{1, valid_profiles}.IRIDIUM_POSITIONS = iridium_positions;
+        clear iridium_positions
+        %% IRIDIUM_DATA_FORMAT
+        nemo_float{1, valid_profiles}.IRIDIUM_DATA_FORMAT = ...
+            iridium_data_format_struct;
+        nemo_float{1, valid_profiles}.IRIDIUM_DATA = iridium_best;
+        clear iridium_best
+        
+        
+    catch exception
+        % something went wrong, so complain and return false
+        warning([mfilename ': ' exception.message]);
+        nemo_float = false;
+        return;
+    end
+    
+    
+end
+
+% if there is no profile data at all, return false
+if valid_profiles == 0
+    nemo_float = false;
+    return;
+end
+
+%% Sort float
+nemo_float = nemo.float.sort_profile_data(nemo_float);
+nemo_float = nemo.float.sort_by_profile_number(nemo_float);
+
+%% Exit this function
+
+return; % end of combined_float2nemostruct
+
+%% Helper functions
+
+    function [ value ] = store_if_available(p_search, p_needle)
+        % if the given needle is a member of search, return the containing value.
+        if isfield(p_search, p_needle)
+            value = p_search.(p_needle);
+            switch class(value)
+                case 'char'
+                    value = strtrim(value);
+            end
+            return
+        else
+            value = NaN;
+            return
+        end
+    end
+
+
+%% FLOAT_IDENTIFICATION
+
+    function [ awi_struct ] = float_identification(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        
+        if isfield( ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname),'DEVICE') ...
+                && strcmp(strtrim( ...
+                  p_combined_float.DECODED_SBD.(p_profile_fieldname).DEVICE),'NEMO Standard' ...
+                  )
+            awi_struct.transmission_type = 'IRIDIUM SBD';
+        else
+            awi_struct.transmission_type = NaN;
+        end
+        
+        if isfield(p_combined_float.GENERAL_INFO, 'IMEI')
+            awi_struct.transmission_id_number_dec = ...
+                p_combined_float.GENERAL_INFO.IMEI;
+        else
+            awi_struct.transmission_id_number_dec = NaN;
+        end
+        
+        awi_struct.transmission_id_number_hex = NaN;
+        
+        awi_struct.wmo_id_number = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'WMO_ID');
+        
+        awi_struct.wmo_instrument_type = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'WMO_INSTRUMENT_TYPE');
+        
+        
+        
+        awi_struct.wmo_recorder_type = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'WMO_RECORD_TYPE');
+        
+        awi_struct.instrument_type = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'INSTRUMENT_TYPE');
+        
+        awi_struct.float_manufacturer = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'FLOAT_MANUFACTURER');
+        
+        awi_struct.float_serial_number = store_if_available( ...
+            p_combined_float.GENERAL_INFO, 'SERIAL');
+        
+        awi_struct.ice_detection_software = store_if_available( ...
+            p_combined_float.SENSOR_INFO, 'ICE_DETECTION');
+        
+        
+        awi_struct.float_provider = store_if_available( ...
+            p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_NAME');
+        
+        
+        awi_struct.float_provider_institution = store_if_available( ...
+            p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_INSTITUTE');
+        
+        awi_struct.originating_country = store_if_available( ...
+            p_combined_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_COUNTRY');
+        
+        
+    end
+
+%% OVERALL_MISSION_INFORMATION
+
+    function [ awi_struct ] = overall_mission_information(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        if isfield(p_combined_float.DECODED_SBD.(p_profile_fieldname), 'TIME_CYCLE')
+            awi_struct.total_cycle = [ ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).TIME_CYCLE/(60*24) ...
+                0 0];
+        else
+            awi_struct.total_cycle = NaN;
+        end
+        
+        awi_struct.down_time = NaN;
+        awi_struct.up_time = NaN;
+        awi_struct.transmission_repetition_rate = NaN;
+        awi_struct.clock_drift = NaN;
+        awi_struct.nominal_drift_depth = NaN;
+        awi_struct.max_departure_from_drift_depth = NaN;
+        
+        awi_struct.nominal_profile_depth = store_if_available( ...
+            p_combined_float.MISSION_PARAMETERS, 'PROFILE_PRESSURE');
+        
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'START_DATE')
+            tmp_mat = strsplit(p_combined_float.DEPLOYMENT_INFO.START_DATE, '.');
+            awi_struct.start_date = [ ...
+                str2double(tmp_mat{1}) ...
+                str2double(tmp_mat{2}) ...
+                str2double(tmp_mat{3})];
+            clear tmp_mat
+        else
+            awi_struct.start_date = NaN;
+        end
+        
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'START_TIME')
+            tmp_mat = strsplit(p_combined_float.DEPLOYMENT_INFO.START_TIME, ':');
+            awi_struct.start_time = [ ...
+                str2double(tmp_mat{1}) ...
+                str2double(tmp_mat{2})];
+            clear tmp_mat
+        else
+            awi_struct.start_time = NaN;
+        end
+        
+        awi_struct.RAFOS_clock_offset_at_start = NaN;
+        awi_struct.start_surface_pressure = NaN;
+        
+        awi_struct.ice_detection_temperature_threshold = store_if_available( ...
+            p_combined_float.MISSION_PARAMETERS, 'ICE_TEMPERATURE');
+        
+        
+    end
+
+%% DEPLOYMENT_INFO
+    function [ awi_struct ] = deployment_info()
+        
+        awi_struct.float_deployer = store_if_available( ...
+            p_combined_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_NAME');
+        
+        awi_struct.float_deployer_institution = store_if_available( ...
+            p_combined_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_INSTITUTE');
+        
+        awi_struct.deployment_platform_type = store_if_available( ...
+            p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM');
+        
+        awi_struct.deployment_platform_code = store_if_available( ...
+            p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_SIGN');
+        
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_EXPEDITION')
+            tmp_string = strsplit( ...
+                p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION, ...
+                '/');
+            if length(tmp_string) == 2
+                awi_struct.deployment_platform_cruise_name = tmp_string{1};
+                try
+                    awi_struct.deployment_platform_leg = str2double(tmp_string{2});
+                catch
+                    awi_struct.deployment_platform_leg = tmp_string{2};
+                end
+            else
+                awi_struct.deployment_platform_cruise_name = ...
+                    p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION;
+                awi_struct.deployment_platform_leg = 'none';
+            end
+            clear tmp_string
+        else
+            awi_struct.deployment_platform_cruise_name = NaN;
+            awi_struct.deployment_platform_leg = 'none';
+        end
+        
+        awi_struct.deployment_ctd_station = NaN;
+        
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_DATE')
+            tmp_string = strsplit( ...
+                p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_DATE, ...
+                '.');
+            awi_struct.deployment_date = [ str2double(tmp_string{1}) ...
+                str2double(tmp_string{2}) ...
+                str2double(tmp_string{3}) ];
+            clear tmp_string
+        else
+            awi_struct.deployment_date = NaN;
+        end
+        
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_TIME')
+            tmp_string = strsplit( ...
+                p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_TIME, ...
+                ':');
+            awi_struct.deployment_time = [ str2double(tmp_string{1}) ...
+                str2double(tmp_string{2}) ];
+            clear tmp_string
+        else
+            awi_struct.deployment_time = NaN;
+        end
+
+        if isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LAT') ...
+                && isfield(p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LON')
+            awi_struct.deployment_position = [ ...
+                p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_LAT ...
+                p_combined_float.DEPLOYMENT_INFO.DEPLOYMENT_LON];
+        else
+            awi_struct.deployment_position = NaN;
+        end
+
+        awi_struct.deployment_speed = NaN;
+        
+        awi_struct.deployment_sea_state = store_if_available( ...
+            p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_STATE');
+        
+        awi_struct.deployment_wave_height = NaN;
+        
+        awi_struct.deployment_ice_coverage = store_if_available( ...
+            p_combined_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_ICE');
+        
+        awi_struct.delay_of_first_down_time = NaN;
+        
+    end
+
+%% PROFILE_TECHNICAL_DATA
+
+    function [ awi_struct ] = profile_technical_data(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        awi_struct.xmit_profile_number = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PROFILE_NUMBER');
+        
+        awi_struct.xmit_serial_number = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'SERIAL');
+        
+        awi_struct.xmit_upcast_status = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'STATUS_UPCAST');
+        
+        awi_struct.xmit_older_profiles_not_send = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PROFILE_OLDER');
+        
+        awi_struct.xmit_motor_errors_count = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_MOTOR');
+        
+        awi_struct.xmit_sbe42_ctd_errors = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_CTD');
+        
+        awi_struct.xmit_o2_optode_errors = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_OPTODE');
+        
+        awi_struct.xmit_rafos_errors = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_RAFOS');
+        
+        awi_struct.xmit_cpu_battery_voltage = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'BATT_CPU');
+        
+        awi_struct.xmit_pump_battery_voltage = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'BATT_PMP');
+        
+        awi_struct.xmit_motor_current = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'CURR_MOTOR');
+        
+        awi_struct.xmit_motor_current_mean = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'CURR_MOTOR_MEAN');
+        
+        awi_struct.xmit_internal_pressure_surface = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_TUBE_SURFACE');
+        
+        awi_struct.xmit_internal_pressure_depth = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_TUBE_DEPTH');
+        
+        awi_struct.xmit_pressure_offset = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_OFFSET');
+        
+        awi_struct.xmit_surface_pressure = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_SURFACE');
+        
+        awi_struct.xmit_parking_pressure_median = median(store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'DS2_PRES'));
+        
+        awi_struct.xmit_depth_pressure = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_DEPTH');
+        
+        awi_struct.xmit_depth_pressure_max = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_MAX');
+        
+        awi_struct.xmit_profile_recovery = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'RECOVERY_START');
+        
+        awi_struct.xmit_piston_counts_surface = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_SURFACE');
+        
+        awi_struct.xmit_piston_counts_parking = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_PARKING');
+        
+        awi_struct.xmit_piston_counts_calc = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_CALC');
+        
+        awi_struct.xmit_piston_counts_eop = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_EOP');
+        
+        awi_struct.xmit_descent_start_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_DESCENT');
+        
+        awi_struct.xmit_parking_start_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_PARKING');
+        
+        awi_struct.xmit_upcast_start_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_SAG');
+        
+        awi_struct.xmit_ascent_start_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_ASCENT');
+        
+        awi_struct.xmit_ascent_end_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_ASCENT_END');
+        
+        awi_struct.xmit_surface_start_time = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_SURFACE');
+        
+        if isfield(p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+                'DATA_RECORDS2') ...
+                && length(p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2) == 5
+            awi_struct.xmit_profile_length_1 = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(1);
+            awi_struct.xmit_profile_length_2 = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(2);
+            awi_struct.xmit_profile_length_3 = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(3);
+            awi_struct.xmit_profile_length_4 = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(4);
+            awi_struct.xmit_profile_length_5 = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(5);
+        else
+            awi_struct.xmit_profile_length_1 = NaN;
+            awi_struct.xmit_profile_length_2 = NaN;
+            awi_struct.xmit_profile_length_3 = NaN;
+            awi_struct.xmit_profile_length_4 = NaN;
+            awi_struct.xmit_profile_length_5 = NaN;
+        end
+        
+        awi_struct.xmit_surface_detection_GPS = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICEGPS_TIME');
+        
+        awi_struct.xmit_surface_detection_Iridium = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICEIRDM_TIME');
+        
+        awi_struct.xmit_airpump_runtime = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'AIRPUMP_RUNTIME');
+        
+        awi_struct.xmit_ice_detection_temp_median = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICE_TEMPMEDIAN');
+        
+    end
+
+%% BOTTOM_VALUES_DURING_DRIFT
+    function [ awi_struct ] = bottom_values_during_drift(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        awi_struct.xmit_bottom_pressure = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_PRES');
+        
+        awi_struct.xmit_bottom_salt = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_SAL');
+        
+        awi_struct.xmit_bottom_temperature = store_if_available( ...
+            p_combined_float.DECODED_SBD.(p_profile_fieldname), 'DS2_TEMP');
+        
+    end
+
+%% RAFOS_VALUES_FORMAT
+
+    function [ awi_struct ] = rafos_values_format()
+        
+        awi_struct = { ...
+            'status';  'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; ...
+            'rtc_minute'; 'rtc_second'; '1STCORHEIGHT'; '1STTRAVELTIME'; ...
+            '2NDCORHEIGHT'; '2NDTRAVELTIME'; '3RDCORHEIGHT'; '3RDTRAVELTIME'; ...
+            '4THCORHEIGHT'; '4THTRAVELTIME'; '5THCORHEIGHT'; '5THTRAVELTIME'; ...
+            '6THCORHEIGHT'; '6THTRAVELTIME'; 'pressure'; 'salinity'; 'temp' ...
+            }';
+        
+    end
+
+%% RAFOS_VALUES
+
+    function [ awi_struct ] = rafos_values(p_profile_fieldname)
+        
+        p = p_combined_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS3_DATE_year') && isfield(p, 'DS3_DATE_month') ...
+                && isfield(p, 'DS3_DATE_day') && isfield(p, 'DS3_TIME') ...
+                && isfield(p, 'DS3_STATUS') && isfield(p, 'DS3_RAFOSAMP') ...
+                && isfield(p, 'DS3_RAFOSRANK') && isfield(p, 'DS3_PRES') ...
+                && isfield(p, 'DS3_SAL') && isfield(p, 'DS3_TEMP')
+            
+            y = (1/(4836-4464) .* p.DS3_DATE_year) + 2000;
+            mon = p.DS3_DATE_month;
+            d = p.DS3_DATE_day;
+            
+            h = floor(p.DS3_TIME./3600);
+            m = floor((p.DS3_TIME - (h*3600))/60);
+            s = uint8(mod(p.DS3_TIME,60));
+            
+            prefix = [p.DS3_STATUS', y', mon', d', h', m', double(s')];
+            
+            awi_struct = [ ...
+                prefix, ...
+                p.DS3_RAFOSAMP(:, 1), p.DS3_RAFOSRANK(:, 1), ...
+                p.DS3_RAFOSAMP(:, 2), p.DS3_RAFOSRANK(:, 2), ...
+                p.DS3_RAFOSAMP(:, 3), p.DS3_RAFOSRANK(:, 3), ...
+                p.DS3_RAFOSAMP(:, 4), p.DS3_RAFOSRANK(:, 4), ...
+                p.DS3_RAFOSAMP(:, 5), p.DS3_RAFOSRANK(:, 5), ...
+                p.DS3_RAFOSAMP(:, 6), p.DS3_RAFOSRANK(:, 6), ...
+                p.DS3_PRES', p.DS3_SAL', p.DS3_TEMP' ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% PROFILE_HEADER
+
+    function [ awi_struct ] = profile_header()
+        
+        awi_struct = {};
+        
+        awi_struct.number_of_columns = 13;
+        awi_struct.column_1 = 'line_number';
+        awi_struct.column_2 = 'hour';
+        awi_struct.column_3 = 'minute';
+        awi_struct.column_4 = 'second';
+        awi_struct.column_5 = 'temperature [deg C]';
+        awi_struct.column_6 = 'temperature flag';
+        awi_struct.column_7 = 'pressure [dbar]';
+        awi_struct.column_8 = 'pressure flag';
+        awi_struct.column_9 = 'salinity [psu]';
+        awi_struct.column_10 = 'salinity flag';
+        awi_struct.column_11 = 'temperature raw';
+        awi_struct.column_12 = 'pressure raw';
+        awi_struct.column_13 = 'salinity raw';
+        awi_struct.temperature_conversion_equation = 'T=cnts/1000, if(cnts>62535),cnts=cnts-65536';
+        awi_struct.pressure_conversion_equation = 'P=cnts/10';
+        awi_struct.salinity_conversion_equation = 'S=cnts/1000';
+        
+    end
+
+%% QUALITY_CONTROL_HEADER
+
+    function [ awi_struct ] = qualtiy_control_header()
+        
+        awi_struct = {};
+        
+        awi_struct.overall_flag_for_T = 0;
+        awi_struct.overall_flag_for_P = 0;
+        awi_struct.overall_flag_for_S = 0;
+        awi_struct.maximum_speed_check_flag = 0;
+        awi_struct.maximum_analysis_check_T_flag = 0;
+        awi_struct.maximum_climatology_T_flag = 0;
+        awi_struct.maximum_gross_check_T_flag = 0;
+        awi_struct.maximum_vertical_gradient_T_flag = 0;
+        awi_struct.maximum_spike_check_T_flag = 0;
+        awi_struct.maximum_analysis_check_S_flag = 0;
+        awi_struct.maximum_climatology_S_flag = 0;
+        awi_struct.maximum_gross_check_S_flag = 0;
+        awi_struct.maximum_vertical_gradient_S_flag = 0;
+        awi_struct.maximum_spike_check_S_flag = 0;
+        awi_struct.sigma_used_for_T_climatology = 10;
+        awi_struct.sigma_used_for_T_analysis = 10;
+        awi_struct.sigma_used_for_S_climatology = 10;
+        awi_struct.sigma_used_for_S_analysis = 10;
+        
+    end
+
+%% PROFILE_DATA_HEADER
+
+    function [ awi_struct ] = profile_data_header()
+        
+        awi_struct = { ...
+            '#'; 'hour'; 'minute'; 'second'; 'temp'; 'temp_flag'; 'pressure'; ...
+            'pres_flag'; 'salinity'; 'sal_flag'; 'temp_raw'; 'pres_raw'; ...
+            'sal_raw'; 'light_442_nm'; 'light_550_nm'; 'light_676_nm' ...
+            }';
+        
+    end
+
+%% PROFILE_DATA
+
+    function [ awi_struct ] = profile_data(p_profile_fieldname)
+        
+        p = p_combined_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS4_TIME') && isfield(p, 'DS4_TEMP') ...
+                && isfield(p, 'DS4_PRES') && isfield(p, 'DS4_SAL')
+                        
+            h = floor(p.DS4_TIME./3600);
+            m = floor((p.DS4_TIME - (h*3600))/60);
+            s = uint8(mod(p.DS4_TIME, 60));
+            profile_numbers = 1:length(h);
+            flags = zeros(1, length(h));
+            
+            prefix = [profile_numbers', h', m', double(s')];
+            
+            % somtimes LICH1-3 has different length than TEMP, PRES & SAL
+            if ~isfield(p, 'DS4_LICH1') || ~isfield(p, 'DS4_LICH2') ...
+                 || ~isfield(p, 'DS4_LICH3') || (length(p.DS4_LICH1) ~= length(p.DS4_TEMP))
+                LICH1 = flags;
+                LICH2 = flags;
+                LICH3 = flags;
+            else
+                LICH1 = p.DS4_LICH1;
+                LICH2 = p.DS4_LICH2;
+                LICH3 = p.DS4_LICH3;
+            end
+            
+            awi_struct = [ ...
+                prefix, ...
+                p.DS4_TEMP', flags', ...
+                p.DS4_PRES', flags', ...
+                p.DS4_SAL', flags', ...
+                p.DS4_TEMP', p.DS4_PRES', p.DS4_SAL', ...
+                LICH1', LICH2', LICH3' ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% SURFACE_GPS_DATA_FORMAT
+
+    function [ awi_struct ] = surface_gps_data_format()
+        
+        awi_struct = { ...
+            '#'; 'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; 'rtc_minute'; ...
+            'rtc_second'; 'GPS_year'; 'GPS_month'; 'GPS_day'; 'GPS_hour'; ...
+            'GPS_minute'; 'GPS_second'; 'lat'; 'lon' ...
+            }';
+        
+    end
+
+%% SURFACE_GPS_DATA
+
+    function [ awi_struct ] = surface_gps_data(p_profile_fieldname)
+        
+        p = p_combined_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS5_GPS') && isfield(p, 'DS5_RTC') ...
+                && isfield(p, 'DS5_LAT') && isfield(p, 'DS5_LON')
+            
+            gps_datevec = datevec(p.DS5_GPS);
+            % check if gps date is correct
+            if gps_datevec(1) == 1999 && gps_datevec(2) == 12 ...
+                    && gps_datevec(3) == 31
+                gps_datevec = [NaN, NaN, NaN, NaN, NaN, NaN];
+            end
+            
+            awi_struct = [ ...
+                1, ...
+                datevec(p.DS5_RTC), ...
+                gps_datevec, ...
+                p.DS5_LAT, ...
+                p.DS5_LON ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% IRIDIUM_POSITIONS_FORMAT
+
+    function [ awi_struct ] = iridium_positions_format()
+        
+        awi_struct = { ...
+            'sbd_lat'; 'sbd_lon'; 'sbd_cep'; ...
+            'yyyy'; 'mm'; 'dd'; ...
+            'hh'; 'mm'; 'ss'
+            }';
+        
+    end
+
+%% IRIDIUM_POSITIONS
+
+    function [ iridium_positions, iridium_best ] = ...
+            iridium_positions_and_data(p_profile_fieldname)
+        
+        iridium_positions = [];
+        iridium_best = [];
+        
+        p = p_combined_float.DECODED_SBD.(p_profile_fieldname);
+        sbd_info = p_combined_float.SBD_MESSAGES_INFO;
+        
+        if (isa(p_combined_float.TRANSMISSION_INFO, 'struct'))
+            p_combined_float.TRANSMISSION_INFO = struct2table(p_combined_float.TRANSMISSION_INFO);
+        end
+        
+        % BUGFIX, delete all rows with empty profilenumber_lsb values
+        sbd_info = sbd_info(~cellfun('isempty', {sbd_info.profilenumber_lsb}));
+        % get all profile numbers of the transmitted iridium messsages
+        sbd_info = struct2table(sbd_info);
+        % be sure that it is sorted!
+        sbd_info = sortrows(sbd_info,'momsn','ascend');
+        % get all corresponding messages
+        iridium_messages = sbd_info(sbd_info.profilenumber_lsb == p.PROFILE_NUMBER, :);
+        iridium_messages = sortrows(iridium_messages, 'momsn', 'ascend');
+        iridium_messages_count = size(iridium_messages, 1);
+       
+        % filter incomplete transmissions
+        first_sbd_index = 1;
+        tmp_index = 0;
+        sbd_total_num = iridium_messages.total_num(first_sbd_index);
+        for i = 1:iridium_messages_count
+            current_elementary = iridium_messages.elementary_num(i);
+            if (current_elementary ~= iridium_messages.elementary_num(first_sbd_index) + tmp_index)
+                first_sbd_index = i;
+                sbd_total_num = iridium_messages.total_num(first_sbd_index);
+                tmp_index = 1;
+            else
+                tmp_index = tmp_index + 1;
+            end
+            if (tmp_index == sbd_total_num)
+                %tmp_index = tmp_index - 1;
+                break;
+            end
+        end
+        
+        iridium_messages = iridium_messages(first_sbd_index:first_sbd_index + tmp_index - 1, :);
+        
+        clear iridium_messages_count;
+        
+        xmit_info = table();
+        
+        iridium_messages_count = size(iridium_messages, 1);
+        for o_iridium = 1:iridium_messages_count
+            current_momsn = iridium_messages(o_iridium, :).momsn;
+            current_xmit_info = p_combined_float.TRANSMISSION_INFO( ...
+                p_combined_float.TRANSMISSION_INFO.MOMSN == current_momsn ...
+                , :);
+            
+            index = 1;
+            if size(current_xmit_info, 1) > 1
+                warning([mfilename ': More than one SBD Message with MOMSN ' current_momsn ' has been found!']);
+                %[~, index] = min(current_xmit_info.CEPRAD);
+            end
+            
+            xmit_info = [xmit_info; current_xmit_info(index, :)];
+        end
+        
+        if (isempty(xmit_info))
+            iridium_positions = [];
+            iridium_best = [];
+            return
+        end
+        
+        % create return structure for all positions
+        iridium_positions = [ ...
+            xmit_info.IRIDIUM_LAT, ...
+            xmit_info.IRIDIUM_LON, ...
+            xmit_info.CEPRAD, ...
+            xmit_info.TIMEOFSESSION ...
+            ];
+        
+        % get best position & time
+        iridium_best = iridium_positions(iridium_positions(:, 3) == min(iridium_positions(:, 3)), :);
+        iridium_best = iridium_best(end, :);
+        % get closest time to the surface time
+        iridium_best(4:9) = iridium_positions(1, 4:9);
+        
+        
+    end
+
+%% IRIDIUM_DATA_FORMAT
+
+    function [ awi_struct ] = iridium_data_format()
+        
+        awi_struct = { ...
+            'mean_lat'; 'mean_lon'; 'mean_cep'; ...
+            'yyyy'; 'mm'; 'dd'; ...
+            'hh'; 'mm'; 'ss'
+            }';
+        
+    end
+
+%% SORT IRIDIUM MESSAGES
+
+    function [ profile_numbers_sorted, indices ] = sort_iridium_messages(p_profile_fieldnames)
+        profile_numbers_unsorted = [];
+        tmp_valid_profile_count = 0;
+        % collect all profile numbers
+        for o_fields = 1:length(p_profile_fieldnames)
+            current_profile = ...
+                p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields});
+            if ~strcmp(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).DATATYPE, 'Profile Message') ...
+                    || ~isfield(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}), 'PROFILE_NUMBER') ...
+                    || isnan(p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER)
+                continue;
+            end
+            tmp_valid_profile_count = tmp_valid_profile_count + 1;
+            profile_numbers_unsorted(tmp_valid_profile_count, :) = ...
+                [p_combined_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER, ...
+                o_fields];
+        end
+        % sort them
+        if ~isempty(profile_numbers_unsorted)
+            [profile_numbers_sorted, indices] = sortrows(profile_numbers_unsorted, 1);
+            indices = profile_numbers_sorted(:, 2);
+        else
+            indices = false;
+            profile_numbers_sorted = false;
+        end
+    end
+
+    function [best_ctd_indices] = filter_multiple_profiles(sorted_indices)
+        % initialize return variable
+        best_ctd_indices = [];
+        best_indices_count = 0;
+        % get profile number of the first element
+        if isfield(p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}), 'DATA_RECORDS2') ...
+                && p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).DATA_RECORDS2(4) > 0
+            % if it is < 0, there is no possibility to compare CTD data
+            best_indices_count = best_indices_count + 1;
+            last_profile_number = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).PROFILE_NUMBER;
+            best_ctd_indices(best_indices_count) = sorted_indices(1);
+        end
+        
+        for o_index = 2:length(sorted_indices)
+            if ~isfield(p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}), 'DATA_RECORDS2') ...
+                || p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4) == 0
+                continue
+            end
+            % get current profile number
+            current_profile_number = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).PROFILE_NUMBER;
+            if best_indices_count > 0 && current_profile_number == last_profile_number
+                % check which ctd data is better
+                current_ctd_length = p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4);
+                last_ctd_length = p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DATA_RECORDS2(4);
+                if current_ctd_length > last_ctd_length
+                    best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+                elseif current_ctd_length == last_ctd_length
+                    % take the one without or less NaN's
+                    tmp_current = [ ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TIME; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_FORMAT; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_PRES; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TEMP; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_SAL ...
+                    ];
+                    tmp_last = [ ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TIME; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_FORMAT; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_PRES; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TEMP; ...
+                        p_combined_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_SAL ...
+                        ];
+                    sum_current = sum(sum(isnan(tmp_current)));
+                    sum_last = sum(sum(isnan(tmp_last)));
+                    if sum_last > sum_current
+                        best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+                    end
+                    clear tmp_current tmp_last sum_current sum_last
+                        
+                end
+            else
+                best_indices_count = best_indices_count + 1;
+                best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+            end
+            
+            last_profile_number = current_profile_number;
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/float.m b/lib/vendor/nemo2profilelib/+nemo/+convert/float.m
new file mode 100644
index 0000000000000000000000000000000000000000..b1a25153869b00984a8f289879f05dbee79b0810
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+convert/float.m
@@ -0,0 +1,969 @@
+function [ awi_float ] = float( p_optimare_float )
+%FLOAT Converts the given float in optimare structure to the structre of AWI.
+%   This is only a temporary function and will be deprecated as soon as the
+%   decoding process has been rewritten. During the decoding, optimare created a
+%   variable structre that differs from the one AWI is using. This function
+%   transfers all variables into the AWI format. In addition to that, it also
+%   checks for incomplete profiles and always saves the profile with the biggest
+%   size of CTD data.
+%
+%   Parameters:
+%   p_optimare_float:   Structure that was created during the decoding process.
+%
+%   Returns:
+%   awi_float:   Structure with the structure that is being used by AWI. False
+%                if there is no data, no profiles or if something went wrong
+%                during conversion.
+
+%% Initialize return variables
+awi_float = {};
+
+%% Convert information to AWI structure
+
+if ~isfield(p_optimare_float, 'DECODED_SBD')
+    % no data available
+    awi_float = false;
+    return
+end
+% get fieldnames of every decoded profile
+profile_fieldnames = fieldnames(p_optimare_float.DECODED_SBD);
+
+% sort iridium messages by profile number
+[profile_numbers_sorted, sorted_indices] = sort_iridium_messages(profile_fieldnames);
+
+if ~sorted_indices
+    % something went wrong, for example only incomplete iridium messages available
+    awi_float = false;
+    return
+end
+
+% get profiles with most CTD data if multiple ones have been transmitted
+best_ctd_indices = filter_multiple_profiles(sorted_indices);
+
+% count valid profiles
+valid_profile_count = length(best_ctd_indices);
+
+
+%% Get format data that is always the same
+deployment_info_struct = deployment_info();
+rafos_values_format_struct = rafos_values_format();
+profile_header_struct = profile_header();
+quality_control_header_struct = qualtiy_control_header();
+profile_data_header_struct = profile_data_header();
+surface_gps_data_format_struct = surface_gps_data_format();
+iridium_positions_format_struct = iridium_positions_format();
+iridium_data_format_struct = iridium_data_format();
+
+% create variable, counting available valid profiles
+valid_profiles = 0;
+
+for o_profile = 1:valid_profile_count
+    % get index of current profile, may not be same as o_profile because optimare
+	% structure is not sorted by profile numbers
+    current_index = best_ctd_indices(o_profile);
+	
+    if ~strcmp(p_optimare_float.DECODED_SBD.(profile_fieldnames{current_index}).DATATYPE, ...
+            'Profile Message')
+			% everything that is not a profile message is being ignored
+            continue
+    else
+		% increase number of valid profiles
+        valid_profiles = valid_profiles + 1;
+    end
+    try % converting the input structure
+	
+        %% FLOAT_IDENTIFICATION
+        awi_float{1, valid_profiles}.FLOAT_IDENTIFICATION = ...
+            float_identification(profile_fieldnames{current_index});
+        %% OVERALL_MISSION_INFORMATION
+        awi_float{1, valid_profiles}.OVERALL_MISSION_INFORMATION = ...
+            overall_mission_information(profile_fieldnames{current_index});
+        %% DEPLOYMENT_INFO
+        awi_float{1, valid_profiles}.DEPLOYMENT_INFO = ...
+            deployment_info_struct;
+        %% PROFILE_TECHNICAL_DATA
+        awi_float{1, valid_profiles}.PROFILE_TECHNICAL_DATA = ...
+            profile_technical_data(profile_fieldnames{current_index});
+        %% BOTTOM_VALUES_DURING_DRIFT
+        awi_float{1, valid_profiles}.BOTTOM_VALUES_DURING_DRIFT = ...
+            bottom_values_during_drift(profile_fieldnames{current_index});
+        %% RAFOS_VALUES_FORMAT
+        awi_float{1, valid_profiles}.RAFOS_VALUES_FORMAT = ...
+            rafos_values_format_struct;
+        %% RAFOS_VALUES
+        awi_float{1, valid_profiles}.RAFOS_VALUES = ...
+            rafos_values(profile_fieldnames{current_index});
+        %% PROFILE_HEADER
+        awi_float{1, valid_profiles}.PROFILE_HEADER = ...
+            profile_header_struct;
+        %% QUALITY_CONTROL_HEADER
+        awi_float{1, valid_profiles}.QUALITY_CONTROL_HEADER = ...
+            quality_control_header_struct;
+        %% PROFILE_DATA_HEADER
+        awi_float{1, valid_profiles}.PROFILE_DATA_HEADER = ...
+            profile_data_header_struct;
+        %% PROFILE_DATA
+        awi_float{1, valid_profiles}.PROFILE_DATA = ...
+            profile_data(profile_fieldnames{current_index});
+			
+        % if there is no profile data, clean up and go to next profile
+        if isempty(awi_float{1, valid_profiles}.PROFILE_DATA)
+            % skip profiles without data
+            awi_float{1, valid_profiles} = [];
+            valid_profiles = valid_profiles - 1;
+            continue
+        end
+		
+        %% SURFACE_GPS_DATA_FORMAT
+        awi_float{1, valid_profiles}.SURFACE_GPS_DATA_FORMAT = ...
+            surface_gps_data_format_struct;
+        %% SURFACE_GPS_DATA
+        awi_float{1, valid_profiles}.SURFACE_GPS_DATA = ...
+            surface_gps_data(profile_fieldnames{current_index});
+        %% IRIDIUM_POSITIONS_FORMAT
+        awi_float{1, valid_profiles}.IRIDIUM_POSITIONS_FORMAT = ...
+            iridium_positions_format_struct;
+        %% IRIDIUM_POSITIONS & IRIDIUM_DATA
+        [ iridium_positions, iridium_best ] = ...
+            iridium_positions_and_data(profile_fieldnames{current_index});
+        awi_float{1, valid_profiles}.IRIDIUM_POSITIONS = iridium_positions;
+        clear iridium_positions
+        %% IRIDIUM_DATA_FORMAT
+        awi_float{1, valid_profiles}.IRIDIUM_DATA_FORMAT = ...
+            iridium_data_format_struct;
+        awi_float{1, valid_profiles}.IRIDIUM_DATA = iridium_best;
+        clear iridium_best
+        
+        
+    catch exception
+        % something went wrong, so complain by returning false
+        awi_float = false;
+        return;
+    end
+    
+    
+end
+
+% if there is no profile data at all, return false
+if valid_profiles == 0
+    awi_float = false;
+    return;
+end
+
+    function [ value ] = store_if_available(p_search, p_needle)
+        % if the given needle is a member of search, return the containing value.
+        if isfield(p_search, p_needle)
+            value = p_search.(p_needle);
+            switch class(value)
+                case 'char'
+                    value = strtrim(value);
+            end
+            return
+        else
+            value = NaN;
+            return
+        end
+    end
+
+
+%% FLOAT_IDENTIFICATION
+
+    function [ awi_struct ] = float_identification(p_profile_fieldname)
+        
+        % abbreveations usually not allowed, this time used for better overview
+        awi_struct = {};
+        
+        awi_struct.internal_ID_number = store_if_available( ...
+            p_optimare_float.DECODED_SBD, 'internal_ID_number');
+        
+        if isfield( ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname),'DEVICE') ...
+                && strcmp(strtrim( ...
+                  p_optimare_float.DECODED_SBD.(p_profile_fieldname).DEVICE),'NEMO Standard' ...
+                  )
+            awi_struct.transmission_type = 'IRIDIUM SBD';
+        else
+            awi_struct.transmission_type = NaN;
+        end
+        
+        if isfield(p_optimare_float.GENERAL_INFO, 'IMEI')
+            awi_struct.transmission_id_number_dec = ...
+                p_optimare_float.GENERAL_INFO.IMEI;
+            awi_struct.transmission_id_number_hex = NaN;
+        else
+            awi_struct.transmission_id_number_dec = NaN;
+            awi_struct.transmission_id_number_hex = NaN;
+        end
+        
+        awi_struct.wmo_id_number = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'WMO_ID');
+        
+        awi_struct.wmo_instrument_type = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'WMO_INSTRUMENT_TYPE');
+        
+        
+        
+        awi_struct.wmo_recorder_type = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'WMO_RECORD_TYPE');
+        
+        awi_struct.instrument_type = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'INSTRUMENT_TYPE');
+        
+        awi_struct.float_manufacturer = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'FLOAT_MANUFACTURER');
+        
+        awi_struct.float_serial_number = store_if_available( ...
+            p_optimare_float.GENERAL_INFO, 'SERIAL');
+        
+        awi_struct.ice_detection_software = store_if_available( ...
+            p_optimare_float.SENSOR_INFO, 'ICE_DETECTION');
+        
+        
+        awi_struct.float_provider = store_if_available( ...
+            p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_NAME');
+        
+        
+        awi_struct.float_provider_institution = store_if_available( ...
+            p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_INSTITUTE');
+        
+        awi_struct.originating_country = store_if_available( ...
+            p_optimare_float.PROVIDER_DEPLOYER_INFO, 'PROVIDER_COUNTRY');
+        
+        
+    end
+
+%% OVERALL_MISSION_INFORMATION
+
+    function [ awi_struct ] = overall_mission_information(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        if isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'TIME_CYCLE')
+            awi_struct.total_cycle = [ ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).TIME_CYCLE/(60*24) ...
+                0 0];
+        else
+            awi_struct.total_cycle = NaN;
+        end
+        
+        awi_struct.down_time = NaN;
+        awi_struct.up_time = NaN;
+        awi_struct.transmission_repetition_rate = NaN;
+        awi_struct.clock_drift = NaN;
+        awi_struct.nominal_drift_depth = NaN;
+        awi_struct.max_departure_from_drift_depth = NaN;
+        
+        awi_struct.nominal_profile_depth = store_if_available( ...
+            p_optimare_float.MISSION_PARAMETERS, 'PROFILE_PRESSURE');
+        
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'START_DATE')
+            tmp_mat = strsplit(p_optimare_float.DEPLOYMENT_INFO.START_DATE, '.');
+            awi_struct.start_date = [ ...
+                str2double(tmp_mat{1}) ...
+                str2double(tmp_mat{2}) ...
+                str2double(tmp_mat{3})];
+            clear tmp_mat
+        else
+            awi_struct.start_date = NaN;
+        end
+        
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'START_TIME')
+            tmp_mat = strsplit(p_optimare_float.DEPLOYMENT_INFO.START_TIME, ':');
+            awi_struct.start_time = [ ...
+                str2double(tmp_mat{1}) ...
+                str2double(tmp_mat{2})];
+            clear tmp_mat
+        else
+            awi_struct.start_time = NaN;
+        end
+        
+        awi_struct.RAFOS_clock_offset_at_start = NaN;
+        awi_struct.start_surface_pressure = NaN;
+        
+        awi_struct.ice_detection_temperature_threshold = store_if_available( ...
+            p_optimare_float.MISSION_PARAMETERS, 'ICE_TEMPERATURE');
+        
+        
+    end
+
+%% DEPLOYMENT_INFO
+    function [ awi_struct ] = deployment_info()
+        
+        awi_struct.float_deployer = store_if_available( ...
+            p_optimare_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_NAME');
+        
+        awi_struct.float_deployer_institution = store_if_available( ...
+            p_optimare_float.PROVIDER_DEPLOYER_INFO, 'DEPLOYER_INSTITUTE');
+        
+        awi_struct.deployment_platform_type = store_if_available( ...
+            p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM');
+        
+        awi_struct.deployment_platform_code = store_if_available( ...
+            p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_SIGN');
+        
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_PLATFORM_EXPEDITION')
+            tmp_string = strsplit( ...
+                p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION, ...
+                '/');
+            if length(tmp_string) == 2
+                awi_struct.deployment_platform_cruise_name = tmp_string{1};
+                awi_struct.deployment_platform_leg = tmp_string{2};
+            else
+                awi_struct.deployment_platform_cruise_name = ...
+                    p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_PLATFORM_EXPEDITION;
+                awi_struct.deployment_platform_leg = 'none';
+            end
+            clear tmp_string
+        else
+            awi_struct.deployment_platform_cruise_name = NaN;
+            awi_struct.deployment_platform_leg = 'none';
+        end
+        
+        awi_struct.deployment_ctd_station = NaN;
+        
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_DATE')
+            tmp_string = strsplit( ...
+                p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_DATE, ...
+                '.');
+            awi_struct.deployment_date = [ str2double(tmp_string{1}) ...
+                str2double(tmp_string{2}) ...
+                str2double(tmp_string{3}) ];
+            clear tmp_string
+        else
+            awi_struct.deployment_date = NaN;
+        end
+        
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_TIME')
+            tmp_string = strsplit( ...
+                p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_TIME, ...
+                ':');
+            awi_struct.deployment_time = [ str2double(tmp_string{1}) ...
+                str2double(tmp_string{2}) ];
+            clear tmp_string
+        else
+            awi_struct.deployment_time = NaN;
+        end
+
+        if isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LAT') ...
+                && isfield(p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_LON')
+            awi_struct.deployment_position = [ ...
+                p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_LAT ...
+                p_optimare_float.DEPLOYMENT_INFO.DEPLOYMENT_LON];
+        else
+            awi_struct.deployment_position = NaN;
+        end
+
+        awi_struct.deployment_speed = NaN;
+        
+        awi_struct.deployment_sea_state = store_if_available( ...
+            p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_STATE');
+        
+        awi_struct.deployment_wave_height = NaN;
+        
+        awi_struct.deployment_ice_coverage = store_if_available( ...
+            p_optimare_float.DEPLOYMENT_INFO, 'DEPLOYMENT_SEA_ICE');
+        
+        awi_struct.delay_of_first_down_time = NaN;
+        
+    end
+
+%% PROFILE_TECHNICAL_DATA
+
+    function [ awi_struct ] = profile_technical_data(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        awi_struct.xmit_profile_number = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PROFILE_NUMBER');
+        
+        awi_struct.xmit_serial_number = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'SERIAL');
+        
+        awi_struct.xmit_upcast_status = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'STATUS_UPCAST');
+        
+        awi_struct.xmit_older_profiles_not_send = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PROFILE_OLDER');
+        
+        awi_struct.xmit_motor_errors_count = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_MOTOR');
+        
+        awi_struct.xmit_sbe42_ctd_errors = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_CTD');
+        
+        awi_struct.xmit_o2_optode_errors = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_OPTODE');
+        
+        awi_struct.xmit_rafos_errors = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ERROR_RAFOS');
+        
+        awi_struct.xmit_cpu_battery_voltage = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'BATT_CPU');
+        
+        awi_struct.xmit_pump_battery_voltage = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'BATT_PMP');
+        
+        awi_struct.xmit_motor_current = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'CURR_MOTOR');
+        
+        awi_struct.xmit_motor_current_mean = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'CURR_MOTOR_MEAN');
+        
+        awi_struct.xmit_internal_pressure_surface = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_TUBE_SURFACE');
+        
+        awi_struct.xmit_internal_pressure_depth = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_TUBE_DEPTH');
+        
+        awi_struct.xmit_pressure_offset = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_OFFSET');
+        
+        awi_struct.xmit_surface_pressure = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_SURFACE');
+        
+        awi_struct.xmit_parking_pressure_median = median(store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'DS2_PRES'));
+        
+        awi_struct.xmit_depth_pressure = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_DEPTH');
+        
+        awi_struct.xmit_depth_pressure_max = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PRES_MAX');
+        
+        awi_struct.xmit_profile_recovery = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'RECOVERY_START');
+        
+        awi_struct.xmit_piston_counts_surface = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_SURFACE');
+        
+        awi_struct.xmit_piston_counts_parking = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_PARKING');
+        
+        awi_struct.xmit_piston_counts_calc = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_CALC');
+        
+        awi_struct.xmit_piston_counts_eop = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'PIST_EOP');
+        
+        awi_struct.xmit_descent_start_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_DESCENT');
+        
+        awi_struct.xmit_parking_start_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_PARKING');
+        
+        awi_struct.xmit_upcast_start_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_SAG');
+        
+        awi_struct.xmit_ascent_start_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_ASCENT');
+        
+        awi_struct.xmit_ascent_end_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_ASCENT_END');
+        
+        awi_struct.xmit_surface_start_time = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'MCNT_SURFACE');
+        
+        if isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+                'DATA_RECORDS2') ...
+                && length(p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2) == 5
+            awi_struct.xmit_profile_length_1 = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(1);
+            awi_struct.xmit_profile_length_2 = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(2);
+            awi_struct.xmit_profile_length_3 = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(3);
+            awi_struct.xmit_profile_length_4 = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(4);
+            awi_struct.xmit_profile_length_5 = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldname).DATA_RECORDS2(5);
+        else
+            awi_struct.xmit_profile_length_1 = NaN;
+            awi_struct.xmit_profile_length_2 = NaN;
+            awi_struct.xmit_profile_length_3 = NaN;
+            awi_struct.xmit_profile_length_4 = NaN;
+            awi_struct.xmit_profile_length_5 = NaN;
+        end
+        
+        awi_struct.xmit_surface_detection_GPS = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICEGPS_TIME');
+        
+        awi_struct.xmit_surface_detection_Iridium = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICEIRDM_TIME');
+        
+        awi_struct.xmit_airpump_runtime = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'AIRPUMP_RUNTIME');
+        
+        awi_struct.xmit_ice_detection_temp_median = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), ...
+            'ICE_TEMPMEDIAN');
+        
+    end
+
+%% BOTTOM_VALUES_DURING_DRIFT
+    function [ awi_struct ] = bottom_values_during_drift(p_profile_fieldname)
+        
+        awi_struct = {};
+        
+        awi_struct.xmit_bottom_pressure = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_PRES');
+        
+        awi_struct.xmit_bottom_salt = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_SAL');
+        
+        awi_struct.xmit_bottom_temperature = store_if_available( ...
+            p_optimare_float.DECODED_SBD.(p_profile_fieldname), 'DS2_TEMP');
+        
+    end
+
+%% RAFOS_VALUES_FORMAT
+
+    function [ awi_struct ] = rafos_values_format()
+        
+        awi_struct = { ...
+            'status';  'rtc_year '; 'rtc_month'; 'rtc_day'; 'rtc_hour'; ...
+            'rtc_minute'; 'rtc_second'; '1STCORHEIGHT'; '1STTRAVELTIME'; ...
+            '2NDCORHEIGHT'; '2NDTRAVELTIME'; '3RDCORHEIGHT'; '3RDTRAVELTIME'; ...
+            '4THCORHEIGHT'; '4THTRAVELTIME'; '5THCORHEIGHT'; '5THTRAVELTIME'; ...
+            '6THCORHEIGHT'; '6THTRAVELTIME'; 'pressure'; 'salinity'; 'temp' ...
+            }';
+        
+    end
+
+%% RAFOS_VALUES
+
+    function [ awi_struct ] = rafos_values(p_profile_fieldname)
+        
+        p = p_optimare_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS3_DATE_year') && isfield(p, 'DS3_DATE_month') ...
+                && isfield(p, 'DS3_DATE_day') && isfield(p, 'DS3_TIME') ...
+                && isfield(p, 'DS3_STATUS') && isfield(p, 'DS3_RAFOSAMP') ...
+                && isfield(p, 'DS3_RAFOSRANK') && isfield(p, 'DS3_PRES') ...
+                && isfield(p, 'DS3_SAL') && isfield(p, 'DS3_TEMP')
+            
+            y = (1/(4836-4464) .* p.DS3_DATE_year) + 2000;
+            mon = p.DS3_DATE_month;
+            d = p.DS3_DATE_day;
+            
+            h = floor(p.DS3_TIME./3600);
+            m = floor((p.DS3_TIME - (h*3600))/60);
+            s = uint8(mod(p.DS3_TIME,60));
+            
+            prefix = [p.DS3_STATUS', y', mon', d', h', m', double(s')];
+            
+            awi_struct = [ ...
+                prefix, ...
+                p.DS3_RAFOSAMP(:, 1), p.DS3_RAFOSRANK(:, 1), ...
+                p.DS3_RAFOSAMP(:, 2), p.DS3_RAFOSRANK(:, 2), ...
+                p.DS3_RAFOSAMP(:, 3), p.DS3_RAFOSRANK(:, 3), ...
+                p.DS3_RAFOSAMP(:, 4), p.DS3_RAFOSRANK(:, 4), ...
+                p.DS3_RAFOSAMP(:, 5), p.DS3_RAFOSRANK(:, 5), ...
+                p.DS3_RAFOSAMP(:, 6), p.DS3_RAFOSRANK(:, 6), ...
+                p.DS3_PRES', p.DS3_SAL', p.DS3_TEMP' ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% PROFILE_HEADER
+
+    function [ awi_struct ] = profile_header()
+        
+        awi_struct = {};
+        
+        awi_struct.number_of_columns = 13;
+        awi_struct.column_1 = 'line_number';
+        awi_struct.column_2 = 'hour';
+        awi_struct.column_3 = 'minute';
+        awi_struct.column_4 = 'second';
+        awi_struct.column_5 = 'temperature [deg C]';
+        awi_struct.column_6 = 'temperature flag';
+        awi_struct.column_7 = 'pressure [dbar]';
+        awi_struct.column_8 = 'pressure flag';
+        awi_struct.column_9 = 'salinity [psu]';
+        awi_struct.column_10 = 'salinity flag';
+        awi_struct.column_11 = 'temperature raw';
+        awi_struct.column_12 = 'pressure raw';
+        awi_struct.column_13 = 'salinity raw';
+        awi_struct.temperature_conversion_equation = 'T=cnts/1000, if(cnts>62535),cnts=cnts-65536';
+        awi_struct.pressure_conversion_equation = 'P=cnts/10';
+        awi_struct.salinity_conversion_equation = 'S=cnts/1000';
+        
+    end
+
+%% QUALITY_CONTROL_HEADER
+
+    function [ awi_struct ] = qualtiy_control_header()
+        
+        awi_struct = {};
+        
+        awi_struct.overall_flag_for_T = 0;
+        awi_struct.overall_flag_for_P = 0;
+        awi_struct.overall_flag_for_S = 0;
+        awi_struct.maximum_speed_check_flag = 0;
+        awi_struct.maximum_analysis_check_T_flag = 0;
+        awi_struct.maximum_climatology_T_flag = 0;
+        awi_struct.maximum_gross_check_T_flag = 0;
+        awi_struct.maximum_vertical_gradient_T_flag = 0;
+        awi_struct.maximum_spike_check_T_flag = 0;
+        awi_struct.maximum_analysis_check_S_flag = 0;
+        awi_struct.maximum_climatology_S_flag = 0;
+        awi_struct.maximum_gross_check_S_flag = 0;
+        awi_struct.maximum_vertical_gradient_S_flag = 0;
+        awi_struct.maximum_spike_check_S_flag = 0;
+        awi_struct.sigma_used_for_T_climatology = 10;
+        awi_struct.sigma_used_for_T_analysis = 10;
+        awi_struct.sigma_used_for_S_climatology = 10;
+        awi_struct.sigma_used_for_S_analysis = 10;
+        
+    end
+
+%% PROFILE_DATA_HEADER
+
+    function [ awi_struct ] = profile_data_header()
+        
+        awi_struct = { ...
+            '#'; 'hour'; 'minute'; 'second'; 'temp'; 'temp_flag'; 'pressure'; ...
+            'pres_flag'; 'salinity'; 'sal_flag'; 'temp_raw'; 'pres_raw'; ...
+            'sal_raw'; 'light_442_nm'; 'light_550_nm'; 'light_676_nm' ...
+            }';
+        
+    end
+
+%% PROFILE_DATA
+
+    function [ awi_struct ] = profile_data(p_profile_fieldname)
+        
+        p = p_optimare_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS4_TIME') && isfield(p, 'DS4_TEMP') ...
+                && isfield(p, 'DS4_PRES') && isfield(p, 'DS4_SAL')
+                        
+            h = floor(p.DS4_TIME./3600);
+            m = floor((p.DS4_TIME - (h*3600))/60);
+            s = uint8(mod(p.DS4_TIME, 60));
+            profile_numbers = 1:length(h);
+            flags = zeros(1, length(h));
+            
+            prefix = [profile_numbers', h', m', double(s')];
+            
+            % somtimes LICH1-3 has different length than TEMP, PRES & SAL
+            if ~isfield(p, 'DS4_LICH1') || ~isfield(p, 'DS4_LICH2') ...
+                 || ~isfield(p, 'DS4_LICH3') || (length(p.DS4_LICH1) ~= length(p.DS4_TEMP))
+                LICH1 = flags;
+                LICH2 = flags;
+                LICH3 = flags;
+            else
+                LICH1 = p.DS4_LICH1;
+                LICH2 = p.DS4_LICH2;
+                LICH3 = p.DS4_LICH3;
+            end
+            
+            awi_struct = [ ...
+                prefix, ...
+                p.DS4_TEMP', flags', ...
+                p.DS4_PRES', flags', ...
+                p.DS4_SAL', flags', ...
+                p.DS4_TEMP', p.DS4_PRES', p.DS4_SAL', ...
+                LICH1', LICH2', LICH3' ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% SURFACE_GPS_DATA_FORMAT
+
+    function [ awi_struct ] = surface_gps_data_format()
+        
+        awi_struct = { ...
+            '#'; 'rtc_year'; 'rtc_month'; 'rtc_day'; 'rtc_hour'; 'rtc_minute'; ...
+            'rtc_second'; 'GPS_year'; 'GPS_month'; 'GPS_day'; 'GPS_hour'; ...
+            'GPS_minute'; 'GPS_second'; 'lat'; 'lon' ...
+            }';
+        
+    end
+
+%% SURFACE_GPS_DATA
+
+    function [ awi_struct ] = surface_gps_data(p_profile_fieldname)
+        
+        p = p_optimare_float.DECODED_SBD.(p_profile_fieldname);
+        if isfield(p, 'DS5_GPS') && isfield(p, 'DS5_RTC') ...
+                && isfield(p, 'DS5_LAT') && isfield(p, 'DS5_LON')
+            
+            gps_datevec = datevec(p.DS5_GPS);
+            % check if gps date is correct
+            if gps_datevec(1) == 1999 && gps_datevec(2) == 12 ...
+                    && gps_datevec(3) == 31
+                gps_datevec = [NaN, NaN, NaN, NaN, NaN, NaN];
+            end
+            
+            awi_struct = [ ...
+                1, ...
+                datevec(p.DS5_RTC), ...
+                gps_datevec, ...
+                p.DS5_LAT, ...
+                p.DS5_LON ...
+                ];
+        else
+            awi_struct = [];
+        end
+        
+        
+    end
+
+%% IRIDIUM_POSITIONS_FORMAT
+
+    function [ awi_struct ] = iridium_positions_format()
+        
+        awi_struct = { ...
+            'sbd_lat'; 'sbd_lon'; 'sbd_cep'; ...
+            'yyyy'; 'mm'; 'dd'; ...
+            'hh'; 'mm'; 'ss'
+            }';
+        
+    end
+
+%% IRIDIUM_POSITIONS
+
+    function [ iridium_positions, iridium_best ] = ...
+            iridium_positions_and_data(p_profile_fieldname)
+        
+        p = p_optimare_float.DECODED_SBD.(p_profile_fieldname);
+        sbd_info = p_optimare_float.SBD_MESSAGES_INFO;
+        
+        if ~isfield(p_optimare_float.TRANSMISSION_INFO, 'MOMSN') ...
+                || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'TIMEOFSESSION') ...
+                || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'IRIDIUM_LAT') ...
+                || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'IRIDIUM_LON') ...
+                || ~isfield(p_optimare_float.TRANSMISSION_INFO, 'CEPRAD')
+            return; % one of the fields is not available
+        end
+        
+        % get transmission info
+        momsn = p_optimare_float.TRANSMISSION_INFO.MOMSN;
+        time_of_session = p_optimare_float.TRANSMISSION_INFO.TIMEOFSESSION;
+        lat = p_optimare_float.TRANSMISSION_INFO.IRIDIUM_LAT;
+        lon = p_optimare_float.TRANSMISSION_INFO.IRIDIUM_LON;
+        cep = p_optimare_float.TRANSMISSION_INFO.CEPRAD;
+        
+        sbd_info_count = length(sbd_info);
+    
+        ir_lat = [];
+        ir_lon = [];
+        ir_cep = [];
+        ir_time = [];
+        ir_time_datevec = [];
+        
+        % get all profile numbers of the transmitted iridium messsages
+        table_sbd_info = struct2table(sbd_info);
+        profile_number_lsb = table_sbd_info(:, {'profilenumber_lsb'});
+        % transform them to be able to use it as comparison
+        profile_number_lsb = table2array(profile_number_lsb);
+        % compare them to the current one
+        tmp_logical_array = (profile_number_lsb(:) == p.PROFILE_NUMBER);
+        % get all corresponding messages
+        iridium_messages = table_sbd_info(tmp_logical_array, :);
+        iridium_messages_count = size(iridium_messages, 1);
+        
+        for o_iridium = 1:iridium_messages_count
+            tmp_logical_array = (momsn == iridium_messages(o_iridium, :).momsn);
+            % get current lat
+            current_lat = lat(tmp_logical_array);
+            current_lon = lon(tmp_logical_array);
+            current_cep = cep(tmp_logical_array);
+            current_time = time_of_session(tmp_logical_array);
+            current_time_datevec = datevec(current_time);
+            if length(current_lat) > 1
+                % get line with best cep
+                [~, index] = min(current_cep);
+                current_lat = nanmean(current_lat(index));
+                current_lon = nanmean(current_lon(index));
+                current_cep = nanmean(current_cep(index));
+                current_time = nanmean(current_time(index));
+                current_time_datevec = datevec(current_time);
+            end
+            ir_lat = [ir_lat; current_lat];
+            ir_lon = [ir_lon; current_lon];
+            ir_cep = [ir_cep; current_cep];
+            ir_time = [ir_time; current_time];
+            ir_time_datevec = [ir_time_datevec; current_time_datevec];
+        end
+        
+        
+        if isempty(ir_lat) || isempty(ir_lon) || isempty(ir_cep) ...
+                || isempty(ir_time)
+            iridium_positions = [];
+            iridium_best = [];
+            return
+%         else
+%             % delete all cep > 10, if there is nothing left store all to prevent
+%             % NaN's
+%             tmp_logical_array = (ir_cep < 10);
+%             if sum(tmp_logical_array) > 0
+%                 ir_lat = ir_lat(tmp_logical_array);
+%                 ir_lon = ir_lon(tmp_logical_array);
+%                 ir_cep = ir_cep(tmp_logical_array);
+%                 ir_time = ir_time(tmp_logical_array);
+%                 ir_time_datevec = datevec(ir_time);
+%             end
+        end
+        
+        % create return structure for all positions
+        iridium_positions = [ ...
+            ir_lat, ir_lon, ir_cep, ir_time_datevec ...
+            ];
+        
+        % get best position
+        best_ceps = unique(ir_cep);
+        ix_best = (ir_cep == min(best_ceps));
+        ir_lat_mean = nanmean(ir_lat(ix_best));
+        ir_lon_mean = nanmean(ir_lon(ix_best));
+        ir_cep_mean = nanmean(ir_cep(ix_best));
+        %ir_time_mean = nanmean(ir_time(ix_best));
+        
+        iridium_best = [ ...
+            ir_lat_mean, ir_lon_mean, ir_cep_mean, ir_time_datevec(end, :)
+            ];
+        
+    end
+
+%% IRIDIUM_DATA_FORMAT
+
+    function [ awi_struct ] = iridium_data_format()
+        
+        awi_struct = { ...
+            'mean_lat'; 'mean_lon'; 'mean_cep'; ...
+            'yyyy'; 'mm'; 'dd'; ...
+            'hh'; 'mm'; 'ss'
+            }';
+        
+    end
+
+%% SORT IRIDIUM MESSAGES
+
+    function [ profile_numbers_sorted, indices ] = sort_iridium_messages(p_profile_fieldnames)
+        profile_numbers_unsorted = [];
+        tmp_valid_profile_count = 0;
+        % collect all profile numbers
+        for o_fields = 1:length(p_profile_fieldnames)
+            current_profile = ...
+                p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields});
+            if ~strcmp(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).DATATYPE, 'Profile Message') ...
+                    || ~isfield(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}), 'PROFILE_NUMBER') ...
+                    || isnan(p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER)
+                continue;
+            end
+            tmp_valid_profile_count = tmp_valid_profile_count + 1;
+            profile_numbers_unsorted(tmp_valid_profile_count, :) = ...
+                [p_optimare_float.DECODED_SBD.(p_profile_fieldnames{o_fields}).PROFILE_NUMBER, ...
+                o_fields];
+        end
+        % sort them
+        if ~isempty(profile_numbers_unsorted)
+            [profile_numbers_sorted, indices] = sortrows(profile_numbers_unsorted, 1);
+            indices = profile_numbers_sorted(:, 2);
+        else
+            indices = false;
+            profile_numbers_sorted = false;
+        end
+    end
+
+    function [best_ctd_indices] = filter_multiple_profiles(sorted_indices)
+        % initialize return variable
+        best_ctd_indices = [];
+        best_indices_count = 0;
+        % get profile number of the first element
+        if isfield(p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}), 'DATA_RECORDS2') ...
+                && p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).DATA_RECORDS2(4) > 0
+            % if it is < 0, there is no possibility to compare CTD data
+            best_indices_count = best_indices_count + 1;
+            last_profile_number = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(1)}).PROFILE_NUMBER;
+            best_ctd_indices(best_indices_count) = sorted_indices(1);
+        end
+        
+        for o_index = 2:length(sorted_indices)
+            if ~isfield(p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}), 'DATA_RECORDS2') ...
+                || p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4) == 0
+                continue
+            end
+            % get current profile number
+            current_profile_number = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).PROFILE_NUMBER;
+            if best_indices_count > 0 && current_profile_number == last_profile_number
+                % check which ctd data is better
+                current_ctd_length = p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DATA_RECORDS2(4);
+                last_ctd_length = p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DATA_RECORDS2(4);
+                if current_ctd_length > last_ctd_length
+                    best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+                elseif current_ctd_length == last_ctd_length
+                    % take the one without or less NaN's
+                    tmp_current = [ ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TIME; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_FORMAT; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_PRES; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_TEMP; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{sorted_indices(o_index)}).DS4_SAL ...
+                    ];
+                    tmp_last = [ ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TIME; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_FORMAT; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_PRES; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_TEMP; ...
+                        p_optimare_float.DECODED_SBD.(profile_fieldnames{best_ctd_indices(best_indices_count)}).DS4_SAL ...
+                        ];
+                    sum_current = sum(sum(isnan(tmp_current)));
+                    sum_last = sum(sum(isnan(tmp_last)));
+                    if sum_last > sum_current
+                        best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+                    end
+                    clear tmp_current tmp_last sum_current sum_last
+                        
+                end
+            else
+                best_indices_count = best_indices_count + 1;
+                best_ctd_indices(best_indices_count) = sorted_indices(o_index);
+            end
+            
+            last_profile_number = current_profile_number;
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..ff7c05214d4f25d55dcaae4cdd91cb76b2342131
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+convert/optimare2nemostruct.m
@@ -0,0 +1,48 @@
+function [ boolean, nemo_data ] = optimare2nemostruct( p_optimare_data )
+%OPTIMARE2NEMOSTRUCT Converts the optimare data structure to nemo format.
+%   Converts the given optimare data structure to the nemo data format that
+%   is used by this library.
+% 
+%   Parameters:
+%   p_optimare_float:   Data structure created by the decoding process
+%                       written by optimare. In case of the source
+%                       nemo2profile, this should be
+%                       handles.all_floats.(float_name)
+%
+%   Returns:
+%   boolean:    False if the conversion failed.
+%   nemo_data:  The converted float data.
+%
+
+%% Initialize return variables
+boolean = false;
+nemo_data = {};
+
+%% Parameter check (not implemented yet)
+
+floatnames = fieldnames(p_optimare_data);
+converted_floatcount = 0;
+
+for o_float = 1:length(floatnames)
+    
+    % get current float data
+    current_float = p_optimare_data.(floatnames{o_float});
+    
+    converted_float = nemo.convert.float(current_float);
+        
+    if ~iscell(converted_float)
+        warning(['Error converting ' floatnames{o_float} '!']);
+    else
+        % store it to nemo data
+        converted_floatcount = converted_floatcount + 1;
+        nemo_data{converted_floatcount} = converted_float;
+        clear converted_float
+    end
+
+end
+
+boolean = true;
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/collect.m b/lib/vendor/nemo2profilelib/+nemo/+float/collect.m
new file mode 100644
index 0000000000000000000000000000000000000000..9e8bd64a3b4b5edae94caa9110356edf23ed7871
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/collect.m
@@ -0,0 +1,192 @@
+function [ data ] = collect( p_nemo_float, p_section, p_member, p_options )
+%COLLECT Collects given section and/or member of float for every profile.
+%   Iterates through every profile in the given nemo float and stores the
+%   selected data either into a cell or a matrix depending on the class
+%   that is being collected. This function is helpful if you require one
+%   parameter of the float of all profiles in one variable.
+%
+%   Parameters:
+%   p_nemo_float:        Float from which the data is being collected.
+%   p_section:           String, Section identifier of the data that should
+%                        be stored.
+%   p_member:            String, member of the section that should be
+%                        stored. If this equals an empty string, the
+%                        complete section is being stored.
+%   p_options:           Currently the following options can be passed to
+%                        the function:
+%                        
+%                        'col2mat, {COLUMN_INTEGER}'
+%                        -> collects the given column and combines it to a
+%                        matrix (used for collecting pressure data of all
+%                        profiles for example)
+%
+%                        'combine'
+%                        -> collects the given section/member and combines
+%                        it as a matrix
+%
+%   Returns:
+%   data:                Collected data for every profile of the given
+%                        float.
+
+%% Initialize return variable
+data = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_float) || ~ischar(p_section))
+    parameter_error = true;
+end
+
+if (exist('p_options', 'var') && ~ischar(p_options))
+    parameter_error = true;
+elseif (~exist('p_options', 'var'))
+    p_options = '';
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Collect data
+
+profile_count = length(p_nemo_float);
+
+if (profile_count == 0)
+    return;
+end
+
+% Calculate internal clock if option is given
+if (strcmp(p_section, 'xmitDatevec'))
+    data = collect_and_calculate_xmit_datevec(p_member);
+    return
+end
+
+if ~exist('p_member', 'var') || strcmp(p_member, '') % the whole section is being collected
+    
+    switch class(p_nemo_float{1}.(p_section))
+        
+        case 'double' % it may be possible to store it as matrix
+            % be sure that data is always one line
+            one_line_double = true;
+            column_count = 0;
+            for o_profiles = 1:profile_count
+                if ~isempty(p_nemo_float{o_profiles}.(p_section)) ...
+                        && size(p_nemo_float{o_profiles}.(p_section), 1) ~= 1
+                    one_line_double = false;
+                    break;
+                end
+                if column_count == 0 && ~isempty(p_nemo_float{o_profiles}.(p_section))
+                    column_count = size(p_nemo_float{o_profiles}.(p_section), 2);
+                end
+            end
+            
+            if one_line_double
+                % only one line per profile, so create a matrix
+                data = [];
+                for o_profiles = 1:profile_count
+                    if ~isempty(p_nemo_float{o_profiles}.(p_section))
+                        data(o_profiles, :) = p_nemo_float{o_profiles}.(p_section);
+                    else
+                        data(o_profiles, 1:column_count) = NaN;
+                    end
+                end
+            elseif (is_option_set('col2mat'))
+                % the given column will be collected from all profiles and
+                % combined into a column-wise matrix
+                all_options = get_options(p_options);
+                [column_number, status] = str2int(all_options{2});
+                if (~status)
+                    disp([mfilename ': Cannot convert option number 2 to integer!']);
+                    return
+                end
+                data = [];
+                for o_profiles = 1:profile_count
+                    if isfield(p_nemo_float{o_profiles}, p_section)
+                        data = [data, p_nemo_float{o_profiles}.(p_section)(:, column_number)];
+                    end
+                end
+            elseif (is_option_set('combine'))
+                % user selected to get a matrix containing all values
+                % instead of seperated cells
+                data = [];
+                for o_profiles = 1:profile_count
+                    if isfield(p_nemo_float{o_profiles}, p_section)
+                        data = [data; p_nemo_float{o_profiles}.(p_section)];
+                    end
+                end
+            else
+                % create a struct because there are more lines in every profile
+                data = {};
+                for o_profiles = 1:profile_count
+                    if ~isfield(p_nemo_float{o_profiles}, p_section)
+                        data{o_profiles} = NaN;
+                    else
+                        data{o_profiles} = p_nemo_float{o_profiles}.(p_section);
+                    end
+                end
+            end
+            
+        otherwise
+            data = {};
+            for o_profiles = 1:profile_count
+                data{o_profiles} = p_nemo_float{o_profiles}.(p_section);
+            end
+            
+    end
+else
+    % exact the same passage as above, but in this one there is a member given
+    switch class(p_nemo_float{1}.(p_section).(p_member))
+        
+        case 'double'
+            if size(p_nemo_float{1}.(p_section).(p_member), 1) == 1
+                % only one line, so create a matrix
+                data = [];
+                for o_profiles = 1:profile_count
+                    data(o_profiles, :) = p_nemo_float{o_profiles}.(p_section).(p_member);
+                end
+            else
+                data = {};
+                for o_profiles = 1:profile_count
+                    data{o_profiles} = p_nemo_float{o_profiles}.(p_section).(p_member);
+                end
+            end
+            
+        otherwise
+            data = {};
+            for o_profiles = 1:profile_count
+                data{o_profiles} = p_nemo_float{o_profiles}.(p_section).(p_member);
+            end
+    end
+    
+end
+
+    function boolean = is_option_set(option_string)
+        options = strsplit(p_options, ',');
+        boolean = any(cellfun(@(x) strcmp(x, option_string), options));
+    end
+
+    function options = get_options(option_string)
+        options = strtrim(strsplit(p_options, ','));
+    end
+
+    function data = collect_and_calculate_xmit_datevec(p_xmit_parameter_name)
+        data = NaN(profile_count, 6);
+        for i = 1:profile_count
+            data(i, :) = ...
+                datevec( ...
+                    datetime([ ...
+                              fliplr(p_nemo_float{i}.OVERALL_MISSION_INFORMATION.start_date), ...
+                              p_nemo_float{i}.OVERALL_MISSION_INFORMATION.start_time 0 ...
+                             ]) ...
+                    + seconds(p_nemo_float{i}.PROFILE_TECHNICAL_DATA.(p_xmit_parameter_name)) ...
+                );
+        end
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m b/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m
new file mode 100644
index 0000000000000000000000000000000000000000..adcef52e35974a5a95ddcb9d6c6765b6322fdbac
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/extract_startup_message.m
@@ -0,0 +1,60 @@
+function [ startup_messages ] = extract_startup_message( p_combined_float )
+%DECODE_STARTUP_MESSAGE Extracts the startup messages of the given float
+%   Detailed explanation goes here
+
+%% Initialize return variable
+startup_messages = false;
+
+%% Check if float is not empty
+if ~isfield(p_combined_float, 'DECODED_SBD')
+    % no data available
+    return
+end
+
+%% Search startup messages and get them into the awi structure
+
+startup_messages = {};
+
+% get fieldnames of every profile
+profile_fieldnames = fieldnames(p_combined_float.DECODED_SBD);
+
+for i = 1:length(profile_fieldnames)
+    
+    if ~strcmp( ...
+               p_combined_float.DECODED_SBD.(profile_fieldnames{i}).DATATYPE, ...
+               'Startup Test Message' ...
+              )
+			% everything that is not a profile or a test message is being ignored
+            continue
+    else
+		% extract the message
+        current_message = p_combined_float.DECODED_SBD.(profile_fieldnames{i});
+        startup_messages{end + 1} = struct();
+        startup_messages{end}.float_serial_number = current_message.SERIAL;
+        startup_messages{end}.software_version = current_message.SOFTWARE;
+        startup_messages{end}.real_time_clock = current_message.RTC;
+        startup_messages{end}.piston_max_count = current_message.PISTON_MAX;
+        startup_messages{end}.piston_min_count = current_message.PISTON_MIN;
+        startup_messages{end}.cpu_battery_voltage = current_message.BATT_CPU;
+        startup_messages{end}.pump_battery_voltage = current_message.BATT_PMP;
+        startup_messages{end}.ext_battery_voltage = current_message.BATT_EXT;
+        startup_messages{end}.pressure_tube_surface = current_message.PRES_TUBE_SURFACE;
+        startup_messages{end}.pressure_profile_depth = current_message.PRES_PROFILE;
+        startup_messages{end}.transmission_time = current_message.TIME_TX;
+        startup_messages{end}.parking_sample_time = current_message.TIME_PARKSAMPLE;
+        startup_messages{end}.cycle_time = current_message.TIME_CYCLE;
+        startup_messages{end}.descent_mode = current_message.DESCENT_MODE;
+        startup_messages{end}.start_recovery = current_message.RECOVERY_START;
+        startup_messages{end}.descent_speed = current_message.DESCENT_SPEED;
+        startup_messages{end}.ice_detection_temperature = current_message.TEMP_ICEDETECT;
+        startup_messages{end}.recovery_transmission = current_message.RECOVERY_TX;
+        startup_messages{end}.recovery_no_transmission = current_message.RECOVERY_NOTX;
+        startup_messages{end}.gps_datetime = current_message.GPS_DATETIME;
+        startup_messages{end}.gps_lat = current_message.GPS_LAT;
+        startup_messages{end}.gps_lon = current_message.GPS_LON;
+    end
+    
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m b/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m
new file mode 100644
index 0000000000000000000000000000000000000000..3c7d0f3e1ba4f84a23995e393e1392cf1286e2d7
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/fetch_process_mails.m
@@ -0,0 +1,202 @@
+function [ boolean, float_imeis ] = fetch_process_mails( p_config, p_metadata )
+%FETCH_MAILS Fetches the mails of the floats in p_metadata
+%   Fetches mails of the given floats in p_metadata. For this, javax.mail
+%   package is being used.
+%
+%   Parameters:
+%   p_config:           Configuration structure, must contain the
+%                       following values:
+%                       -username {string}
+%                       -password {string}
+%                       -port {integer}
+%                       -ssl {0 or 1}
+%                       -server {string}
+%                       -imap_timeout {integer}
+%                       -imaps_connection_timeout {integer}
+%                       -storage_directory {string}
+%                       -folder_to_fetch {string}
+%                       -sort_by_imei {0 or 1}
+%                       -separate_sbd_and_txt {0 or 1}
+%                       -mark_read_after_fetch {0 or 1}
+%                       -fetch_unread_only {0 or 1}
+%                       -sender_address {string}
+%                       -use_sender_address {0 or 1}
+%
+%   Returns:
+%   boolean:            True, if successful.
+%   float_imeis         List with length of p_metadata. Contains either 0
+%                       or the IMEI number of the float if there are new
+%                       mails that have been fetched.
+
+%% Initialize return variables
+boolean = false;
+
+%% Parameter check
+if (~isstruct(p_config) || (~isstruct(p_metadata) && ~iscell(p_metadata)))
+    warning([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+end
+
+%% Setup some alias
+fetch_unread_only = p_config.MAIL.fetch_unread_only;
+mark_read_after_fetch = p_config.MAIL.mark_read_after_fetch;
+sort_by_imei = p_config.MAIL.sort_by_imei;
+storage_directory = p_config.MAIL.storage_directory;
+separate_sbd_and_txt = p_config.MAIL.separate_sbd_and_txt;
+
+%% Setup folder
+if (~isdir(storage_directory))
+    mkdir(storage_directory);
+end
+
+%% Connect to mail server
+imapstore = nemo.mail.connect(p_config);
+if (~imapstore.isConnected()) % warning is already given in connect()
+    return;
+end
+
+%% Create search and flag term
+% there are difficulties accessing static class members of an inner java
+% class. This solution has been found at:
+% http://undocumentedmatlab.com/blog/accessing-internal-java-class-members
+if (fetch_unread_only)
+    search_flag = javax.mail.Flags(); % create search flag
+    tmp = search_flag.getClass.getDeclaredClasses;
+    seen_flag = tmp(1).getField('SEEN').get(1);
+    clear tmp;
+    search_flag.add(seen_flag);
+    flag_term = javax.mail.search.FlagTerm(search_flag, false);
+else
+    flag_term = false;
+end
+
+if (~isfield(p_config.MAIL, 'sender_address') || ~isfield(p_config.MAIL, 'use_sender_address'))
+    warning([mfilename ': Config does not include a MAIL.sender_address or MAIL.use_sender_address field!']);
+    return;
+elseif (p_config.MAIL.use_sender_address)
+    java_sender_address = java.lang.String(p_config.MAIL.sender_address);
+    sender_term = javax.mail.search.FromStringTerm(java_sender_address);
+else
+    sender_term = false;
+end
+
+
+
+%% Fetch all mails and save them to disk
+
+switch class(p_metadata)
+    case 'struct'
+        % only one float has to be processed
+        [boolean, float_imeis] = process_float(p_metadata);
+    case 'cell'
+        [booleans, float_imeis] = cellfun(@process_float, p_metadata);
+        if (~all(booleans))
+            warning([mfilname ': Something went wrong during message download! Please contact developer or system administrator!']);
+        else
+            boolean = true;
+        end
+end
+
+clear imapstore;
+
+
+    function [boolean, float_imei] = process_float(p_float_metadata)
+        imei_int = p_float_metadata.GENERAL_INFO.IMEI;
+        imei_str = int2str(imei_int);
+        float_imei = 0; % will be overridden if there are messages to process
+        %% Check for directories
+        if (sort_by_imei)
+            float_storage_directory = [storage_directory imei_str p_config.GENERAL.folder_separator];
+        else
+            float_storage_directory = storage_directory;
+        end
+        % check if folders exists
+        if (~isdir(float_storage_directory))
+            mkdir(float_storage_directory);
+        end
+        if (separate_sbd_and_txt)
+            float_txt_directory = [float_storage_directory 'txt' p_config.GENERAL.folder_separator];
+            float_sbd_directory = [float_storage_directory 'sbd' p_config.GENERAL.folder_separator];
+            float_incomplete_directory = [float_storage_directory 'incomplete' p_config.GENERAL.folder_separator];
+            if (~isdir(float_sbd_directory))
+                mkdir(float_sbd_directory);
+            end
+            if (~isdir(float_txt_directory))
+                mkdir(float_txt_directory);
+            end
+            if (~isdir(float_incomplete_directory))
+                mkdir(float_incomplete_directory);
+            end
+        else
+            float_sbd_directory = float_storage_directory;
+            float_txt_directory = float_sbd_directory;
+        end
+        
+        %% Create term for message filtering
+        subject_term = javax.mail.search.SubjectTerm(imei_str);
+        if isa(flag_term, 'javax.mail.search.FlagTerm') ...
+                && isa(sender_term, 'javax.mail.search.FromStringTerm')
+            tmp_comb = javax.mail.search.AndTerm(subject_term, flag_term);
+            combined_terms = javax.mail.search.AndTerm(tmp_comb, sender_term);
+        elseif isa(flag_term, 'javax.mail.search.FlagTerm')
+            combined_terms = javax.mail.search.AndTerm(flag_term, subject_term);
+        elseif isa(sender_term, 'javax.mail.search.FromStringTerm')
+            combined_terms = javax.mail.search.AndTerm(sender_term, subject_term);
+        else
+            combined_terms = subject_term;
+        end
+        
+        %% Get mail that fit the terms
+        [messages, folder] = nemo.mail.fetch_messages(imapstore, ...
+            p_config.MAIL.folder_to_fetch, ...
+            combined_terms, ...
+            ~mark_read_after_fetch ...
+        );
+    
+        %% Process all messages
+        for i = 1:messages.length
+            [from, ~, inline, attachments] = nemo.mail.extract(messages(i));
+            if (p_config.MAIL.use_sender_address && ~strcmp(from, p_config.MAIL.sender_address))
+                disp([mfilename ': Filter fetched the wrong address: ' from{1} '! Skipping message...']);
+                continue;
+            end
+            
+            if (~isempty(attachments))
+                % store the float serial
+                float_imei = imei_int;
+                filename = char(attachments{1});
+                [~, file, ~] = fileparts(filename);
+                % export inline as well as attachment
+                nemo.mail.export_attachment(attachments{2}, [float_sbd_directory filename]);
+                % save inline
+                fid = fopen([float_txt_directory file '.txt'], 'wt');
+                fprintf(fid, inline);
+                fclose(fid);
+            else % mail has been received but no attachment
+                % export only inline to incomplete folder
+                processed_inline = nemo.mail.process_inline(inline);
+                fid = fopen([float_incomplete_directory int2str(processed_inline.MOMSN) '.txt'], 'wt');
+                fprintf(fid, inline);
+                fclose(fid);
+            end
+            clear from subject inline attachments filename;
+        end
+        
+        if (mark_read_after_fetch && messages.length > 0)
+            % mark processed as read
+            flag = javax.mail.Flags(); % create search flag
+            tmp_class = flag.getClass.getDeclaredClasses;
+            flag_seen = tmp_class(1).getField('SEEN').get(1);
+            clear tmp_class;
+            flag.add(flag_seen);
+            folder.setFlags(messages, flag, true);
+            clear flag
+        end
+        clear messages combined_terms subject_term messages folder;
+        
+        boolean = true;
+        
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/find.m b/lib/vendor/nemo2profilelib/+nemo/+float/find.m
new file mode 100644
index 0000000000000000000000000000000000000000..3725dfa41fc459114843bcbb113964e166193485
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/find.m
@@ -0,0 +1,49 @@
+function [ floats, indices ] = find( p_nemo_data, p_float_number )
+%FIND Returns the float with the given p_float_number.
+%   Searches for the given float in p_nemo_struct.
+%
+%   Parameters:
+%   p_nemo_data:        The nemo data to be searched.
+%   p_float_number:     The float number that is being searched for.
+%                       It can also be a 1D vector of float numbers.
+%
+%   Returns:
+%   float:              Copy from p_nemo_data of the float with the given
+%                       float number.
+%   index:              The index of the float in p_nemo_data.
+
+%index = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.float_serial_number == p_float_number, p_nemo_data);
+
+indices = [];
+floats = {};
+
+float_number_length = length(p_float_number);
+for current_float_number = 1:float_number_length
+    index = cellfun(@(x) find_float(x), p_nemo_data);
+    if (~any(index))
+        continue;
+    elseif (sum(index) > 1)
+        disp(['Multiple floats found: ' index]);
+        return
+    end
+
+    indices = [indices find(index)];
+    floats{end + 1} = p_nemo_data{index};
+    
+
+end
+
+if (length(p_float_number) == 1)
+    floats = floats{1};
+end
+
+    function [ is_float ] = find_float(p_float)
+        if isfield(p_float, 'GENERAL_INFO')
+            is_float = p_float.GENERAL_INFO.SERIAL == p_float_number(current_float_number);
+        else
+            is_float = p_float{1}.FLOAT_IDENTIFICATION.float_serial_number == p_float_number(current_float_number);
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m b/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m
new file mode 100644
index 0000000000000000000000000000000000000000..8e506d8a4ad413ae365629057cdef86da1f587b4
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/get_changed_profiles.m
@@ -0,0 +1,57 @@
+function [ changed_profiles ] = get_changed_profiles( p_config, p_float )
+%GET_CHANGED_PROFILES Loads the old profiles from the harddisk and compares them to the structure given in p_float.
+%   Compares the old profiles that are saved on the harddisk with the given
+%   float in p_float. Compares the profiles by using the
+%   nemo.profile.compare function.
+%
+%   Parameters:
+%   p_config:           The nemo configuration file.
+%   p_float:            The nemo data where the old profiles will be
+%                       compared with.
+%
+%   Returns:
+%   changed_profiles:   Cell array containing the profiles that have
+%                       changed.
+
+%% Initialize return variables
+changed_profiles = false;
+
+%% Parameter check
+if (~isstruct(p_config) || ~iscell(p_float))
+    disp([ mfilename ': Please check your parameter!']);
+    return;
+end
+
+%% Save the float to a temporary folder and load it afterwards
+% this is because the comparison does not work with a float loaded from HDD
+% and directly converted from SBD messages in RAM
+
+
+%% Load old profiles
+
+old_profiles = nemo.load.data( ...
+    [ ...
+        p_config.OUTPUT_FILES.output_path ...
+        sprintf('%04d', p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+    ] ...
+);
+
+%% Compare them to the current ones
+
+old_profiles = nemo.float.sort_by_profile_number(old_profiles);
+p_float = nemo.float.sort_by_profile_number(p_float);
+changed_profiles = {};
+
+for i = 1:length(p_float)
+    current_profile = p_float{i};
+    old_profile = nemo.profile.find(old_profiles, current_profile.PROFILE_TECHNICAL_DATA.xmit_profile_number);
+    if (isa(old_profile, 'logical') && ~old_profile) ...
+            || ~nemo.profile.compare(old_profile, current_profile)
+        changed_profiles{end + 1} = current_profile;
+        continue;
+    end
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m b/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m
new file mode 100644
index 0000000000000000000000000000000000000000..b9f6909e0ae17a671866bc7c85a9cd8075d560cb
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/process_mission_parameter.m
@@ -0,0 +1,127 @@
+function [ mission_parameters ] = process_mission_parameter( p_mission_parameter_string )
+%PROCESS_MISSION_PARAMETER Summary of this function goes here
+%   Detailed explanation goes here
+
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Serial number ','end');
+        mission_parameters.SERIAL=str2num(p_mission_parameter_string(ix:ix+3));
+    catch
+        mission_parameters.SERIAL=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Firmware version ','end');
+        mission_parameters.FIRMWARE_VERSION=str2num(p_mission_parameter_string(ix:ix+6));
+    catch
+        mission_parameters.FIRMWARE_VERSION=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Startup time: ','end');
+        mission_parameters.STARTUP_TIME=str2num(p_mission_parameter_string(ix:ix+4));
+    catch
+        mission_parameters.STARTUP_TIME=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Cycle time: ','end');
+        mission_parameters.START_CYCLETIME=str2num(p_mission_parameter_string(ix:ix+5));
+    catch
+        mission_parameters.START_CYCLETIME=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Change cycletime to ','end');
+        mission_parameters.CYCLETIME=str2num(p_mission_parameter_string(ix:ix+6));
+    catch
+        mission_parameters.CYCLETIME=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'minutes in profile ','end');
+        mission_parameters.CYCLE_CHANGE=str2num(p_mission_parameter_string(ix:ix+3));
+    catch
+        mission_parameters.CYCLE_CHANGE=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Startup delay: ','end');
+        mission_parameters.STARTUP_DELAY=(p_mission_parameter_string(ix:ix+3));
+    catch
+        mission_parameters.STARTUP_DELAY='NaN';
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Startprofiles: ','end');
+        mission_parameters.STARTPROFILES=str2num(p_mission_parameter_string(ix:ix+3));
+    catch
+        mission_parameters.STARTPROFILES=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Profile pressure: ','end');
+        mission_parameters.PROFILE_PRESSURE=str2num(p_mission_parameter_string(ix:ix+5));
+    catch
+        mission_parameters.PROFILE_PRESSURE=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Maximum pressure: ','end');
+        mission_parameters.MAX_PRESSURE=str2num(p_mission_parameter_string(ix:ix+5));
+    catch
+        mission_parameters.MAX_PRESSURE=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'PistonOut @ alert = ','end');
+        mission_parameters.PISTON_OUT_ALERT=str2num(p_mission_parameter_string(ix:ix+4));
+    catch
+        mission_parameters.PISTON_OUT_ALERT=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Last min/max piston counts: ','end');
+        mission_parameters.LAST_PISTON_MIN_MAX=(p_mission_parameter_string(ix:ix+9));
+    catch
+        mission_parameters.LAST_PISTON_MIN_MAX='NaN';
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'RAFOS Start time ','end');
+        mission_parameters.RAFOS_STARTTIME=(p_mission_parameter_string(ix:ix+6));
+    catch
+        mission_parameters.RAFOS_STARTTIME='NaN';
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'Duration ','end');
+        mission_parameters.RAFOS_DURATION=str2num(p_mission_parameter_string(ix:ix+4));
+    catch
+        mission_parameters.RAFOS_DURATION=NaN;
+    end
+    clear ix
+    
+    try
+        ix = regexp(p_mission_parameter_string,'temperature = ','end');
+        mission_parameters.ICE_TEMPERATURE=str2num(p_mission_parameter_string(ix:ix+5));
+    catch
+        mission_parameters.ICE_TEMPERATURE=NaN;
+    end
+    clear ix
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m b/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m
new file mode 100644
index 0000000000000000000000000000000000000000..e6cdac416d50cad33eb59d850f3b30919958feec
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/save_startup_message.m
@@ -0,0 +1,107 @@
+function [ boolean ] = save_startup_message( p_startup_messages, p_filepath, p_overwrite )
+%SAVE_STARTUP_MESSAGES Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize variables
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_startup_messages) || ~ischar(p_filepath))
+    parameter_error = true;
+end
+
+if (~exist('p_overwrite', 'var') || isempty(p_overwrite))
+    p_overwrite = 1; % default setting is to overwrite existing profile files
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% File existence checks
+
+if ~exist(p_filepath, 'dir')
+    mkdir(p_filepath);
+end
+
+%% Configuration parameters
+double_format = '%.15g';
+help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'];
+desired_tab_count = 12;
+
+
+%% Write message to file
+
+%for outer = 1:length(p_startup_messages) % required if there are multiple messages
+current_message = p_startup_messages{end};
+filename = [ ...
+    p_filepath ...
+    sprintf('%04d', current_message.float_serial_number) ...
+    '.startup_message' ...
+    ];
+
+if exist(filename, 'file') == 2 && ~p_overwrite
+    % file will not be overwritten
+    boolean = true;
+    return
+end
+
+of_id = fopen(filename, 'w'); % open file for writing
+fprintf(of_id, '[STARTUP_MESSAGE]\n');
+write_struct(current_message);
+fclose(of_id);
+
+boolean = true;
+%end
+
+    function [ tabs ] = get_tabs_to_insert(p_member_name)
+        % this function computes the required tabs to insert, making the
+        % textfile easier to read.
+        tablength = 4;
+        tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength);
+        tabs = help_tabs(1:(tab_count*2));
+    end
+
+    function write_struct(p_input_struct)
+        % get all fieldnames and write them to the file
+        member_names = fieldnames(p_input_struct);
+        member_count = length(member_names);
+        for o_member = 1:member_count % run through all members
+            % calculate how many tabs have to be inserted to have a
+            % good table layout
+            tabstring = get_tabs_to_insert(member_names{o_member});
+            fprintf(of_id, ['-' member_names{o_member} tabstring]);
+            current_member = p_input_struct.(member_names{o_member});
+            current_member_class = class(current_member);
+            switch current_member_class
+                case {'double', 'logical'}
+                    write_double(current_member);
+                case 'char'
+                    fprintf(of_id, [current_member '\n']);
+                otherwise
+                    disp(['Unknown class in file: ' class(current_member)]);
+            end
+            
+        end
+        clear o_member
+    end
+
+    function write_double(p_input_double)
+        
+        for o_double = 1:size(p_input_double, 1)
+            if length(p_input_double(o_double, :)) > 1
+                fprintf(of_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1)));
+            end
+            fprintf(of_id, [double_format '\n'], p_input_double(o_double, end));
+        end
+        clear o_double
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m b/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m
new file mode 100644
index 0000000000000000000000000000000000000000..d9ece54754999230478c4d5d01cbdf145190cde9
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/sort_by_profile_number.m
@@ -0,0 +1,34 @@
+function [ sorted_float ] = sort_by_profile_number( p_float )
+%SORT_BY_PROFILE_NUMBER Gets the input float and sorts its profile by their numbers
+%   
+%   Parameters:
+%   p_float:        Float that will be sorted.
+%
+%   Returns:
+%   sorted_float:   Sorted float by profile number.
+
+% get all profile numbers
+profile_numbers = nemo.float.collect(p_float, 'PROFILE_TECHNICAL_DATA', 'xmit_profile_number');
+
+% sort them and get the indices
+[~, indices] = sort(profile_numbers);
+
+% create helping variable to check if the float is already sorted
+sorted = [1:length(profile_numbers)]';
+if indices == sorted
+    % indeed it is, so save work and just return the input one :)
+    sorted_float = p_float;
+    return
+end
+
+% sorting required
+sorted_float = {};
+
+% indices have already been taken so just run through them
+for i = 1:length(p_float)
+    sorted_float{i} = p_float{indices(i)};
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m
new file mode 100644
index 0000000000000000000000000000000000000000..4747285eb8aaa49a523383379bd2339dbbfae2cb
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+float/sort_profile_data.m
@@ -0,0 +1,26 @@
+function [ sorted_float ] = sort_profile_data( p_float )
+%SORT_PROFILE_DATA Sorts the profile data of the given float ascending by pressure.
+%   Sorts every CTD profile ascending by pressure of the given float.
+%
+%   Parameters:
+%   p_float:                The float containing the CTD data to be sorted.
+%   
+%   Returns:
+%   sorted_float:           Copy of the float with sorted CTD data.
+
+
+%% Initialize return variable
+sorted_float = false;
+
+%% Parameter check
+if (~iscell(p_float))
+    disp([mfilename ': Unknown input type! Please check your input parameter!']);
+    return
+end
+
+%% Sort profile data by time
+sorted_float = cellfun(@nemo.profile.sort_profile_data, p_float, 'UniformOutput', false);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m b/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m
new file mode 100644
index 0000000000000000000000000000000000000000..0dea90aa701bd8fe03c9548a7f4e51411be71364
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+ftp/upload.m
@@ -0,0 +1,137 @@
+function [ is_successful ] = upload( p_config, p_nemo_data )
+%ftp_server_upload Uploads profiles to the ftp.awi.de server as user argo.
+%   If p_nemo_data is given, only the given profiles will be uploaded.
+%   Otherwise all profiles on the harddisk will be uploaded to the server
+%   as defined in p_config.
+%   Some default names are being ignored.
+%   Existing files on the server will be overwritten. Files
+%   existing ONLY on the server will not be touched.
+%
+%   Parameters:
+%   p_config:           The nemo configuration.
+%   p_nemo_data:        Optional, the nemo data that will be uploaded to
+%                       the server.
+
+
+%% Initialize  return variables
+is_successful = false;
+
+%% Check input parameter
+if (~isstruct(p_config) || ~isfield(p_config, 'FTP')) % display a warning
+    disp([ mfilename ': Please provide valid nemo configuration!']);
+    return;
+end
+
+if (exist('p_files_to_upload', 'var') && ~iscell(p_nemo_data))
+    disp([ mfilename ': Please provide a cell of strings in p_files_to_upload!']);
+    return;
+end
+
+%% Set up server connection
+
+try % connecting to ftp server
+    ftp_server = ftp( ...
+        p_config.FTP.server, ...
+        p_config.FTP.username, ...
+        p_config.FTP.password ...
+    );
+catch error
+    disp([mfilename sprintf(': Failed to connect to server!\n') error]);
+    return;
+end
+
+% change to correct folder if necessary
+if (~strcmp(cd(ftp_server), p_config.FTP.remote_folder))
+    cd(ftp_server, p_config.FTP.remote_folder);
+end
+
+%% Start upload process
+
+if exist('p_nemo_data', 'var') % upload files that have been given
+    for i = 1:length(p_nemo_data)
+        for k = 1:length(p_nemo_data{i})
+            profile_filename = [ ...
+                sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                '_' ...
+                sprintf('%04d', p_nemo_data{i}{k}.PROFILE_TECHNICAL_DATA.xmit_profile_number) ...
+                '.profile'
+                ];
+            mkdir( ...
+                ftp_server, ...
+                [ ...
+                    p_config.FTP.remote_folder ...
+                    p_config.FTP.folder_separator ...
+                    sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                ] ...
+            );
+            cd( ...
+                ftp_server, ...
+                [ ...
+                    p_config.FTP.remote_folder ...
+                    p_config.FTP.folder_separator ...
+                    sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                ] ...
+            );
+            mput(ftp_server, ...
+                 [ ...
+                    p_config.OUTPUT_FILES.output_path ...
+                    sprintf('%04d', p_nemo_data{i}{k}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                    p_config.GENERAL.folder_separator ...
+                    profile_filename ...
+                ] ...
+            );
+        end
+    end
+    
+else
+
+    directory_content = dir(p_config.OUTPUT_FILES.output_path); % should contain all float directories
+    
+    % run through every content of the folder
+    for current = 1:size(directory_content, 1)
+        if (directory_content(current).isdir)
+            % create directory
+            mkdir(ftp_server, ...
+                [ ...
+                p_config.FTP.remote_folder ...
+                p_config.FTP.folder_separator ...
+                directory_content(current).name ...
+                ] ...
+                );
+            % switch to it on the server side
+            cd(ftp_server, [p_config.FTP.remote_folder p_config.FTP.folder_separator directory_content(current).name]);
+            % upload all .profile files
+            current_profiles = dir([p_config.OUTPUT_FILES.output_path directory_content(current).name p_config.GENERAL.folder_separator '*.profile']);
+            for i = 1:size(current_profiles, 1)
+                if (strcmp(current_profiles(i).name, 'Startup_Test_Message.profile') ...
+                        || strcmp(current_profiles(i).name, 'MTM_Report_Message.profile') ...
+                        || strcmp(current_profiles(i).name, 'nemo_0NaN_NaN.profile'))
+                    continue % skip some default names
+                end
+                % upload the file
+                mput(ftp_server, [p_config.OUTPUT_FILES.output_path ...
+                    directory_content(current).name p_config.GENERAL.folder_separator current_profiles(i).name]);
+            end
+        end
+    end
+
+end
+
+
+
+
+%% Close server connection
+
+try % closing the connection
+    close(ftp_server);
+catch error
+    disp([mfilename sprintf(': Closing connection to server failed:\n') getReport(error)]);
+end
+
+is_successful = true;
+
+return
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m b/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m
new file mode 100644
index 0000000000000000000000000000000000000000..e6e1cd83cea7e83f0ae0a6ea37f0a6a6874b0b54
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/additional_content.m
@@ -0,0 +1,29 @@
+function [ additional_content ] = additional_content( p_config )
+%LOAD_ADDITIONAL_CONTENT Loads content that is defined in [ADDITIONAL_CONTENT] section of the global configuration file.
+
+
+%% Initialize return variable
+additional_content = {};
+
+%% Load all additional content
+
+ice_cover_location = p_config.ADDITIONAL_CONTENT.antarctic_sea_ice_coverage_file;
+grid_location = p_config.ADDITIONAL_CONTENT.antarctic_grid_file;
+
+additional_content.antarctic_latitudes = ...
+    hdfread(grid_location, '/Latitudes', 'Index', {[1  1],[1  1],[1328  1264]});
+additional_content.antarctic_longitudes = ...
+    hdfread(grid_location, '/Longitudes', 'Index', {[1  1],[1  1],[1328  1264]});
+additional_content.antarctic_ice_concentration = ...
+    hdfread(ice_cover_location, '/ASI Ice Concentration', 'Index', {[1  1],[1  1],[1328  1264]});
+% get etopo
+[additional_content.etopo_z, additional_content.etopo_refvec] = ...
+    etopo(p_config.ADDITIONAL_CONTENT.etopo_file, 1, ...
+    p_config.PLOT_OVERVIEW_SOUTH.latitude_limit, ...
+    p_config.PLOT_OVERVIEW_SOUTH.longitude_limit);
+% load moorings
+additional_content.moorings = ...
+    nemo.load.moorings(p_config.ADDITIONAL_CONTENT.moorings_file);
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/config.m b/lib/vendor/nemo2profilelib/+nemo/+load/config.m
new file mode 100644
index 0000000000000000000000000000000000000000..2a275470aa6924928a952aee52afbf180a38dd31
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/config.m
@@ -0,0 +1,110 @@
+function [ config ] = config( p_filename, p_force_load )
+%LOAD_CONFIG Loads the given config file.
+%
+%   Parameters:
+%   p_filename:       String, absolute or relative filepath to the target file.
+%                     It is recommended to give the absolute filepath.
+%
+%   Returns:          Struct, containing the sections and member of the
+%                     configuration file.
+%
+
+%% Initialize input parameter
+config = false;
+
+if ~exist('p_force_load', 'var')
+    p_force_load = false;
+end
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_filename))
+    warning([mfilename ': Please check input parameter. p_filename is not a string!']);
+    parameter_error = true;
+elseif ~p_force_load
+    % get filename details
+    [pathstr, name, ext] = fileparts(p_filename);
+    if (~strcmp(ext, '.conf'))
+        warning([mfilename ': Please check input parameter. Wrong file extension! Should be .conf!']);
+        parameter_error = true;
+    end
+end
+
+if parameter_error
+    return; % return if parameter check has not been passed successfully
+else
+    clear parameter_error;
+end
+
+%% Check if file exists
+if exist(p_filename, 'file') ~= 2
+    return;
+end
+
+%% Load configuration into variable
+
+% initialize all required variables
+config = {};
+current_section = '';
+comment_char = '%'; % character to indicate comments
+section_char = '[';
+
+% open file
+file_id = fopen(p_filename);
+fseek(file_id, 0, 'bof');
+
+
+% read file line by line
+current_line = fgetl(file_id);
+while (ischar(current_line))
+    if length(current_line) == 0 || strcmp(current_line(1), comment_char)
+        current_line = fgetl(file_id);
+        continue
+    end
+    
+    switch current_line(1)
+        case section_char % this indicates a new section
+            current_line = strtrim(current_line);
+            current_section = current_line(2:end-1);
+            read_section
+    end
+    %current_line = fgetl(file_id);
+end
+
+fclose(file_id);
+
+    function read_section
+        % read complete section
+        current_line = fgetl(file_id);
+        while ischar(current_line) && ~isempty(current_line) ...
+                && ~strcmp(current_line(1), section_char)
+            if strcmp(current_line(1), comment_char) % it is a comment line
+                current_line = fgetl(file_id);
+                continue
+            end
+            
+            % if it contains a comment char anywhere, just take the string
+            % before it
+            found_comment = strfind(current_line, comment_char);
+            if ~isempty(found_comment)
+                current_line = current_line(1:found_comment(1)-1);
+            end
+            
+            % get variable name and value
+            [token, remain] = strtok(current_line);
+            % try to convert remain to a number
+            remain_as_num = str2num(strtrim(remain));
+            if (~isempty(remain_as_num))
+                config.(current_section).(token(2:end)) = remain_as_num;
+            else
+                config.(current_section).(token(2:end)) = strtrim(remain);
+            end
+                    
+            current_line = fgetl(file_id);
+        end
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/data.m b/lib/vendor/nemo2profilelib/+nemo/+load/data.m
new file mode 100644
index 0000000000000000000000000000000000000000..033be9534423e5a22e4c876d4608e963c90bb716
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/data.m
@@ -0,0 +1,87 @@
+function [ nemo_data ] = data(p_path)
+%LOAD_DATA Loads nemo data from the file structure into memory.
+%
+%   Parameters:
+%   p_path:       Absolute or relative filepath to the file structure
+%                 containing the nemo data.
+%
+%   Returns:      Struct, containing loaded nemo data from the text files.
+
+%% Initialize input parameter
+nemo_data = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_path))
+    warning([mfilename ': Please check input parameter. p_path is not a string!']);
+    parameter_error = true;
+end
+
+if parameter_error
+    return; % return if parameter check has not been passed successfully
+else
+    clear parameter_error;
+end
+
+if (~exist('p_folder_separator', 'var'))
+    p_folder_separator = '\';
+end
+
+% check if last character is a \
+if (~strcmp(p_path(end), p_folder_separator))
+    p_path = [p_path p_folder_separator];
+end
+
+%% Read profiles
+
+nemo_folder_names = dir(p_path); % get all elements in given directory
+folders_to_exclude = {'.', '..'}; % these folders will be excluded from the list
+nemo_folder_names = exclude_files(nemo_folder_names);
+nemo_folder_names = exclude_elements(nemo_folder_names, folders_to_exclude, 'dir');
+
+%% Loop through every float
+
+files_to_exclude = { ...
+    'Startup_Test_Message.profile', ...
+    'MTM_Report_Message.profile', ...
+    'nemo_0NaN_NaN.profile' ...
+    };
+
+if (isempty(nemo_folder_names))
+    nemo_data = nemo.load.single_float(p_path, p_folder_separator, files_to_exclude);
+    return;
+end
+
+nemo_data = {}; % initialize return variable as struct
+nemo_data_count = 0;
+
+for o_float_index = 1:length(nemo_folder_names) % every float directory
+    % getting the absolute path to the current directory
+    current_dir = [p_path nemo_folder_names(o_float_index).name];
+    % get all files in it
+    current_files = dir(fullfile([current_dir p_folder_separator], '*.profile'));
+    current_files = exclude_elements(current_files, files_to_exclude, 'dir');
+        
+    profile_count = length(current_files);
+    
+    if (profile_count > 0)
+        current_profiles = {};
+        
+        for o_profile_index = 1:profile_count
+            % read current profile
+            profile = nemo.profile.load(fullfile([current_dir p_folder_separator], current_files(o_profile_index).name));
+            current_profiles{o_profile_index} = profile;
+            clear profile;
+        end
+        
+        nemo_data_count = nemo_data_count + 1;
+        nemo_data{nemo_data_count} = current_profiles;
+        clear current_profiles;
+    end
+    
+end
+
+return
+
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m b/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..2e6f6bc567171fcb39f95a95c0dbeebe46ca4cf2
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/float_metadata.m
@@ -0,0 +1,34 @@
+function [ float_metadata ] = float_metadata( p_filename )
+%FLOAT_METADATA Loads the given metadata file.
+%   If p_filename is a directory, all files with the .xml extension are
+%   being selected and loaded.
+%
+%   Parameters:
+%   p_filename:             The file or folder to be loaded.
+%
+%   Returns:
+%   float_metadata:         The loaded metadata files in a cell.
+
+%% Initialize return value
+float_metadata = false;
+
+%% Parameter is already being checked in nemo.load.xml_file
+float_metadata = nemo.load.xml_file(p_filename);
+
+switch (class(float_metadata))
+    case 'struct'
+        float_metadata.MISSION_PARAMETERS = ...
+            nemo.float.process_mission_parameter(float_metadata.MISSION_PARAMETER_STRING);
+    case 'cell'
+        for i = 1:length(float_metadata)
+            float_metadata{i}.MISSION_PARAMETERS = ...
+                nemo.float.process_mission_parameter(float_metadata{i}.MISSION_PARAMETER_STRING);
+        end
+    otherwise
+        warning([mfilename ': Something went wrong while loading the xml file(s)!']);
+        return;
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m b/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m
new file mode 100644
index 0000000000000000000000000000000000000000..e03e46bb461ab7466ac3a27869cb772ee92770fd
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/float_transmission_info.m
@@ -0,0 +1,48 @@
+function [ processed_inline ] = float_transmission_info( p_config, p_imei )
+%FLOAT_TRANSMITTION_INFO Reads the transmittion information stored in the mail body. 
+%   Loads all mail bodies of the given IMEI and stores the processed text
+%   in a cell.
+%
+%   Parameters:
+%   p_config:           The nemo configuration.
+%   p_imei:             The IMEI to load the information.
+%
+%   Returns:
+%   processed_inline:   A cell containing the processed information.
+
+%% Initialize return variables
+processed_inline = false;
+
+%% TODO: Parameter check
+
+
+%% Load inline file(s)
+txt = ['txt' p_config.GENERAL.folder_separator];
+if (~ischar(p_imei))
+    imei = int2str(p_imei);
+else
+    imei = p_imei;
+end
+
+% create full filepath
+filepath = p_config.MAIL.storage_directory;
+
+if (p_config.MAIL.sort_by_imei)
+    filepath = [filepath imei p_config.GENERAL.folder_separator];
+end
+if (p_config.MAIL.separate_sbd_and_txt)
+    filepath = [filepath txt];
+end
+filenames = struct2table(dir([filepath '*.txt']));
+if (isempty(filenames.name))
+    return;
+end
+absolute_filenames = cellfun(@(name) [filepath p_config.GENERAL.folder_separator name], filenames.name, 'UniformOutput', false);
+processed_inline = cellfun(@read_file, absolute_filenames);
+
+    function inline = read_file(filename)
+        inline = nemo.mail.process_inline(fileread(filename));
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..35142a58148d8dddd9d04d6c69f36e563023b99a
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/floats2nemostruct.m
@@ -0,0 +1,56 @@
+function [ nemo_struct, startup_messages ] = floats2nemostruct( p_config, p_metadata )
+%SBD2OPTIMARE Converts and combines the float .sbd data into the nemo structure.
+%   Uses nemo.sbd.decode_float function to decode the profiles and
+%   nemo.convert.combined_float2nemostruct to convert the combined profiles
+%   to the nemo structure.
+%
+%   Parameters:
+%   p_config:       The nemo configuration.
+%   p_metadata:     The metadata information about the floats to process.
+%
+%   Returns:
+%   nemo_struct:    The resulting nemo struct.
+
+%% Run through metadata and process every float
+nemo_struct = cell(length(p_metadata), 1);
+startup_messages = cell(length(p_metadata), 1);
+
+if (isa(p_metadata, 'struct'))
+    tmp = p_metadata;
+    p_metadata = {};
+    p_metadata{1} = tmp;
+    clear tmp;
+end
+
+for i = 1:length(p_metadata)
+    % copy all metadata to new struct
+    current_float = p_metadata{i};
+    imei_int = p_metadata{i}.GENERAL_INFO.IMEI;
+    %serial_str = sprintf('%04d', p_metadata{i}.GENERAL_INFO.SERIAL);
+    
+    % collect all information from harddisk
+    [decoded_sbd, sbd_messages_info] = nemo.sbd.decode_float(p_config, imei_int);
+    current_float.DECODED_SBD = decoded_sbd;
+    current_float.SBD_MESSAGES_INFO = sbd_messages_info;
+    transmission_info = nemo.load.float_transmission_info(p_config, imei_int);
+    current_float.TRANSMISSION_INFO = transmission_info;
+    
+    % convert startup messages
+    startup_messages{i} = nemo.float.extract_startup_message(current_float);
+    % convert this information to the nemo structure
+    nemo_struct{i} = nemo.convert.combined_float2nemostruct(current_float);
+    % add startup message
+    if ~isempty(startup_messages{i}) ...
+       && ~isempty(startup_messages{i}{end}) ...
+       && ~isa(nemo_struct{i}, 'logical')
+        nemo_struct{i} = cellfun(@add_startup_message, nemo_struct{i}, 'UniformOutput', false);
+    end
+    
+end
+
+    function [ p_float ] = add_startup_message(p_float)
+        p_float.STARTUP_MESSAGE = startup_messages{i}{end};
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m b/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m
new file mode 100644
index 0000000000000000000000000000000000000000..1aaba6cf2aad32718bcdc9b08c4c706f3bfc042b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/latest_profile.m
@@ -0,0 +1,46 @@
+function [ nemo_profile, profile_count ] = latest_profile( p_config, p_float_number )
+%LATEST_PROFILE Loads the latest profile of the given float.
+%   
+
+%% Initialize return variable
+nemo_profile = {};
+
+%% Parameter check
+if ~isstruct(p_config) || ~isa(p_float_number, 'double')
+    disp([mfilename ': Wrong parameters, please check your input!']);
+    return;
+end
+
+%% Determine the latest profile file
+profile_files = dir( ...
+    [ ...
+        p_config.OUTPUT_FILES.output_path ...
+        sprintf('%04d', p_float_number) ...
+        p_config.GENERAL.folder_separator ...
+        '*.profile' ...
+    ] ...
+);
+
+profile_files = {profile_files.name};
+profile_files = sort(profile_files);
+
+if isempty(profile_files)
+    profile_count = 0;
+    return
+end
+filename = profile_files{end};
+profile_count = length(profile_files);
+
+%% Now load the profile if available
+nemo_profile = nemo.profile.load( ...
+    [ ...
+        p_config.OUTPUT_FILES.output_path ...
+        sprintf('%04d', p_float_number) ...
+        p_config.GENERAL.folder_separator ...
+        filename ...
+    ] ...
+);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m b/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m
new file mode 100644
index 0000000000000000000000000000000000000000..537232cf028855cdecaa0117484dab4ec8b59bb5
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/moorings.m
@@ -0,0 +1,103 @@
+function [ moorings ] = moorings( p_filename )
+%LOAD_MOORINGS Loads all moorings from a file into memory
+%   Almost the same procedure as nemo.load.config(). It has some changes
+%   to make it useful for loading moorings configuration file.
+
+%% Initialize input parameter
+moorings = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_filename))
+    warning([mfilename ': Please check input parameter. p_filename is not a string!']);
+    parameter_error = true;
+else
+    % get filename details
+    if ~exist(p_filename, 'file')
+        warning([mfilename ': Please check input parameter, given file does not exist!']);
+        parameter_error = true;
+    end
+end
+
+if parameter_error
+    return; % return if parameter check has not been passed successfully
+else
+    clear parameter_error;
+end
+
+%% Load profile into variable
+
+% initialize all required variables
+moorings = {};
+current_section = '';
+comment_char = '%'; % character to indicate comments
+section_char = '['; % character indicating new section
+mooring_count = 1;
+
+% open file
+file_id = fopen(p_filename);
+fseek(file_id, 0, 'bof');
+
+
+% read file line by line
+current_line = fgetl(file_id);
+while (ischar(current_line))
+    if length(current_line) == 0 || strcmp(current_line(1), comment_char)
+        current_line = fgetl(file_id);
+        continue
+    end
+    
+    switch current_line(1)
+        case section_char % this indicates a new section
+            current_line = strtrim(current_line);
+            current_section = current_line(2:end-1);
+            moorings{mooring_count}.name = current_section;
+            read_section
+            mooring_count = mooring_count + 1;
+    end
+    %current_line = fgetl(file_id);
+end
+
+fclose(file_id);
+
+    function read_section
+        % read complete section
+        current_line = fgetl(file_id);
+        while ischar(current_line) && ~isempty(current_line) ...
+                && ~strcmp(current_line(1), section_char)
+            
+            % ignore comment char
+            if strcmp(current_line(1), comment_char)
+                current_line = fgetl(file_id);
+                continue
+            end
+            
+            % if it contains a comment char anywhere, just take the string
+            % before it
+            found_comment = strfind(current_line, comment_char);
+            if ~isempty(found_comment)
+                current_line = current_line(1:found_comment(1)-1);
+            end
+            
+            % get variable name and value
+            [token, remain] = strtok(current_line);
+            if (strcmp(token(2:end), 'pong_time'))
+                moorings{mooring_count}.(token(2:end)) = strtrim(remain);
+            else
+                % try to convert remain to a number
+                remain_as_num = str2num(strtrim(remain));
+                if (~isempty(remain_as_num))
+                    moorings{mooring_count}.(token(2:end)) = remain_as_num;
+                else
+                    moorings{mooring_count}.(token(2:end)) = strtrim(remain);
+                end
+            end
+                    
+            current_line = fgetl(file_id);
+        end
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m b/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m
new file mode 100644
index 0000000000000000000000000000000000000000..2baafb21ad11d3391771293e14fbc19ea407c3d1
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/rfb_config.m
@@ -0,0 +1,101 @@
+function [ config ] = rfb_config( p_filename )
+%RFB_CONFIG Loads the given config file.
+%
+%   Parameters:
+%   p_filename:       String, absolute or relative filepath to the target file.
+%                     It is recommended to give the absolute filepath.
+%
+%   Returns:          Struct, containing the sections and member of the
+%                     configuration file.
+%
+
+%% Initialize input parameter
+config = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_filename))
+    warning([mfilename ': Please check input parameter. p_filename is not a string!']);
+    parameter_error = true;
+else
+    % get filename details
+    [pathstr, name, ext] = fileparts(p_filename);
+    if (~strcmp(ext, '.conf'))
+        warning([mfilename ': Please check input parameter. Wrong file extension! Should be .conf!']);
+        parameter_error = true;
+    end
+end
+
+if parameter_error
+    return; % return if parameter check has not been passed successfully
+else
+    clear parameter_error;
+end
+
+%% Load profile into variable
+
+% initialize all required variables
+config = {};
+current_section = '';
+comment_char = '%'; % character to indicate comments
+section_char = '[';
+
+% open file
+file_id = fopen(p_filename);
+fseek(file_id, 0, 'bof');
+
+
+% read file line by line
+current_line = fgetl(file_id);
+while (ischar(current_line))
+    if length(current_line) == 0 || strcmp(current_line(1), comment_char)
+        current_line = fgetl(file_id);
+        continue
+    end
+    
+    switch current_line(1)
+        case section_char % this indicates a new section
+            current_line = strtrim(current_line);
+            current_section = current_line(2:end-1);
+            read_section
+    end
+    %current_line = fgetl(file_id);
+end
+
+fclose(file_id);
+
+    function read_section
+        % read complete section
+        current_line = fgetl(file_id);
+        while ischar(current_line) && ~isempty(current_line) ...
+                && ~strcmp(current_line(1), section_char)
+            if strcmp(current_line(1), comment_char) % it is a comment line
+                current_line = fgetl(file_id);
+                continue
+            end
+            
+            % if it contains a comment char anywhere, just take the string
+            % before it
+            found_comment = strfind(current_line, comment_char);
+            if ~isempty(found_comment)
+                current_line = current_line(1:found_comment(1)-1);
+            end
+            
+            % get variable name and value
+            [token, remain] = strtok(current_line);
+            % try to convert remain to a number
+            remain_as_num = str2num(strtrim(remain));
+            if (~isempty(remain_as_num))
+                config.(current_section).(token(2:end)) = remain_as_num;
+            else
+                config.(current_section).(token(2:end)) = strtrim(remain);
+            end
+                    
+            current_line = fgetl(file_id);
+        end
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m b/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m
new file mode 100644
index 0000000000000000000000000000000000000000..319ed1453440f1e511a7711644b68bbc8a35df9e
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/single_float.m
@@ -0,0 +1,40 @@
+function [ float_data ] = single_float( p_path, p_folder_separator, p_files_to_exclude )
+%SINGLE_FLOAT Loads a float from the harddisk.
+%   Loads all profiles of a float in the given path.
+%
+%   Parameters:
+%   p_path:             The filepath where the .profile files are stored.
+%   p_folder_separator: The folder separator character.
+%   p_files_to_exclude: List of files that will be ignored.
+%
+%   Returns:
+%   float_data:         The loaded profiles.
+
+%% Initialize return variable
+float_data = {};
+
+%% Get all files in the folder and load profiles
+if (~exist('p_folder_separator', 'var'))
+    p_folder_separator = '\';
+end
+current_files = dir(fullfile([p_path p_folder_separator], '*.profile'));
+if (exist('p_files_to_exclude', 'var'))
+    current_files = exclude_elements(current_files, p_files_to_exclude, 'dir');
+end
+
+profile_count = length(current_files);
+
+if (profile_count > 0)
+    float_data = {};
+
+    for o_profile_index = 1:profile_count
+        % read current profile
+        profile = nemo.profile.load(fullfile([p_path p_folder_separator], current_files(o_profile_index).name));
+        float_data{o_profile_index} = profile;
+        clear profile;
+    end
+
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m b/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m
new file mode 100644
index 0000000000000000000000000000000000000000..06e406289676c58f6367d71ce96cbc3abbae28bc
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+load/xml_file.m
@@ -0,0 +1,61 @@
+function [ xml_file ] = xml_file( p_filename, p_filemask )
+%FLOAT_METADATA Loads xml files into a matlab cell.
+%   Loads either one xml file or all xml files from the given directory.
+%
+%   Parameter:
+%   p_filename:     string, if empty the complete directory is being loaded.
+%   p_filemask:     string, the filemask of the files to be selected, for
+%                   example: *float_*
+%   
+%   Returns:
+%   xml_file:       cell, containing all files that have been read.
+
+%% Initialize return variables
+xml_file = false;
+
+%% Parameter check
+parameter_error = false;
+file_only = false;
+
+if (~ischar(p_filename) || (exist('p_filemask', 'var') && ~ischar(p_filemask)))
+    parameter_error = true;
+end
+
+if (~isdir(p_filename))
+    file_only = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Check operating system
+if (ismac || isunix)
+    folder_separator = '/';
+elseif (ispc)
+    folder_separator = '\';
+end
+
+%% Load metadata files
+if (file_only)
+    if ~exist(p_filename, 'file') % warn user
+        warning([mfilename ': Float metadata file ' p_filename ' does not exist!']);
+        return
+    end
+    xml_file = utils.xml_read(p_filename);
+else
+    if (exist('p_filemask', 'var'))
+        filenames = struct2table(dir([p_filename folder_separator p_filemask '.xml']));
+    else
+        filenames = struct2table(dir([p_filename folder_separator '*.xml']));
+    end
+    
+    absolute_filenames = cellfun(@(name) [p_filename folder_separator name], filenames.name, 'UniformOutput', false);
+    xml_file = cellfun(@(name) utils.xml_read(name), absolute_filenames, 'UniformOutput', false);
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m b/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m
new file mode 100644
index 0000000000000000000000000000000000000000..eeb352e1667ee5c9c7fd9257f95c512439ef25d8
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/connect.m
@@ -0,0 +1,62 @@
+function [ imapstore ] = connect( p_config )
+%CONNECT Connects with the specified server details where nemo emails are stored.
+%  This functions connects to the mail server given in the config
+%  parameter. Therefore the java library javax.mail.jar (javaMail) and
+%  activation.jar (JavaBeans Activation Framework) is required. There is no
+%  disconnect function, use 'clear imapstore' instead.
+%  javaMail Documentation: https://javamail.java.net/nonav/docs/api/
+%
+%  Parameter:
+%  p_config:    struct, containing the content of the nemo config file.
+%
+%  Returns:
+%  imapstore:   IMAPSSLSTORE/IMAPSTORE java object, false on fail.
+
+%% Initialize return variable
+imapstore = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+if (~isfield(p_config, 'MAIL'))
+    disp('No MAIL section found in p_config.');
+    return;
+end
+
+%% Prepare connection
+try
+    props = java.util.Properties; % default imap properties
+    props.put('mail.imap.port', p_config.MAIL.port);
+    props.put('mail.imap.timeout', p_config.MAIL.imap_timeout);
+    props.put('mail.imaps.connectiontimeout', p_config.MAIL.imaps_connection_timeout);
+    session = javax.mail.Session.getInstance(props); % create a session
+    if (p_config.MAIL.ssl) % get the ssl store object
+        imapstore = session.getStore('imaps');
+    else % get the store object
+        imapstore = session.getStore('imap');
+    end
+    imapstore.connect( ...
+        p_config.MAIL.server, ...
+        p_config.MAIL.port, ...
+        p_config.MAIL.username, ...
+        p_config.MAIL.password ...
+    );
+catch
+    warning('Something failed during the establishment of the imap connection!');
+    imapstore = false;
+    return;
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m b/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m
new file mode 100644
index 0000000000000000000000000000000000000000..41f96bf54357bdd1bf2af1b72db634e3927583dd
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/export_attachment.m
@@ -0,0 +1,50 @@
+function [ Boolean ] = export_attachment( Attachment, AbsoluteFilename )
+%EXPORT_ATTACHMENT Exports the given attachment to the given filename.
+%   Exports the attachment (type of com.sun.mail.util.BASE64DecoderStream)
+%   to the given filename.
+%
+%   Parameters:
+%   Attachment:         The attachment to be saved.
+%   AbsoluteFilename:   The filename where the attachment is being
+%                       exported to.
+%
+%   Returns:
+%   Boolean:            True if successful.
+
+%% Initialize return variable
+Boolean = false;
+
+%% Check parameter
+
+if (~isa(Attachment, 'com.sun.mail.util.BASE64DecoderStream') ...
+        || ~ischar(AbsoluteFilename))
+    warning([mfilename ': Input parameter check failed!']);
+    return
+end
+
+%% Create file and export attachment to it
+
+[Path, ~, ~] = fileparts(AbsoluteFilename);
+
+if (~isdir(Path)) % create it
+    mkdir(Path);
+end
+
+% save every byte in attachment to file
+File = java.io.File(AbsoluteFilename);
+OutputStream = java.io.FileOutputStream(File);
+
+Byte = Attachment.read();
+while (Byte ~= -1)
+    OutputStream.write(Byte);
+    Byte = Attachment.read();
+end
+
+OutputStream.flush(); % prevent loosing bytes in write process
+OutputStream.close();
+Attachment.close();
+
+Boolean = true;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m b/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m
new file mode 100644
index 0000000000000000000000000000000000000000..0262492ede28bc9a187b81538b51d5be7fd55670
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/extract.m
@@ -0,0 +1,63 @@
+function [ FromArray, Subject, Inline, AttachmentArray ] = extract( Message )
+%EXTRACT Extracts basic information from the given message.
+%   Extracts the message created by the javax.mail library.
+%
+%   Parameters:
+%   Message:        The message to be extracted from.
+%
+%   Returns:
+%   FromArray:          All senders addresses (usually just one)
+%   Subject:            Subject of the message.
+%   Inline:             The message body.
+%   AttachmentArray:    All attachments of the message.
+
+%% Initialize return variables
+FromArray = false;
+Subject = false;
+Inline = '';
+AttachmentArray = false;
+
+%% Get from address
+FromArray = Message.getFrom();
+nFrom = FromArray.length;
+% convert to matlab char
+tmp = {};
+for i = 1:nFrom
+    tmp{i} = char(FromArray(i));
+end
+FromArray = tmp;
+clear tmp;
+
+%% Get subject
+Subject = char(Message.getSubject());
+
+%% Get inline text and attachments
+MessageContent = Message.getContent();
+if (~isempty(strfind(Message.getContentType, 'text/plain')))
+    Inline = MessageContent;
+    AttachmentArray = {};
+    return
+end
+nBodies = MessageContent.getCount();
+if (nBodies > 0)
+    AttachmentArray = {};
+end
+
+for iBody = 0:nBodies - 1
+    BodyPart = MessageContent.getBodyPart(iBody);
+    if (BodyPart.isMimeType('text/plain') ...
+        || BodyPart.isMimeType('text/html'))
+        Inline = char(BodyPart.getContent());
+    elseif (BodyPart.isMimeType('attachment') ...
+            || BodyPart.isMimeType('application/x-zip-compressed'))
+        AttachmentArray{iBody, 1} = BodyPart.getDataHandler.getName();
+        AttachmentArray{iBody, 2} = BodyPart.getDataHandler.getInputStream();
+    else
+        disp([mfilename ': MimeType ' char(BodyPart.getContentType()) ' not supported!']);
+    end
+            
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m
new file mode 100644
index 0000000000000000000000000000000000000000..f39ef0bf2ff02bafbb0755f5f8161b1db056276b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_messages.m
@@ -0,0 +1,69 @@
+function [ messages, folder ] = fetch_messages( p_imapstore, p_foldername, p_searchterm, p_read_only )
+%FETCH_UNREAD Fetches all messages in the given folder.
+%   Fetches all messages that match the given searchterm from the given
+%   folder.
+%
+%   Parameters:
+%   p_imapstore:            The imapstore object of javax.mail library.
+%   p_foldername:           The foldername where the messages will be
+%                           fetched from.
+%   p_searchterm:           The searchterm created with javax.mail library.
+%   p_read_only:            If true, the folder will be opened as read
+%                           only.
+%   
+%   Returns:
+%   messages:               The messages that match the searchterm.
+%   folder:                 The folder object.
+
+%% Initialize return variables
+messages = false;
+
+if (~exist('p_read_only', 'var'))
+    p_read_only = true;
+end
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_foldername))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Check connection
+
+if (~p_imapstore.isConnected())
+    disp('IMAPStore not connected, trying to reconnect...');
+    p_imapstore.connect();
+    if (~p_imapstore.isConnected())
+        warning('IMAPStore reconnect failed. Please check your configuration!');
+        return;
+    else
+        disp('... success!');
+    end
+end
+
+%% Get and open folder
+folder = p_imapstore.getFolder(p_foldername);
+if (~p_read_only)
+    folder.open(folder.READ_WRITE);
+else
+    folder.open(folder.READ_ONLY);
+end
+
+%% Fetch messages
+if (isa(p_searchterm, 'logical') || strcmp(p_searchterm, ''))
+    messages = folder.getMessages();
+else
+    messages = folder.search(p_searchterm);
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m
new file mode 100644
index 0000000000000000000000000000000000000000..0e02e120b89481c93e57d7e8aac988cffc5170c8
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/fetch_unread.m
@@ -0,0 +1,60 @@
+function [ unread_messages, folder ] = fetch_unread( p_imapstore, p_foldername )
+%FETCH_UNREAD Fetches all messages that are flagged as unread on the server.
+%   Fetches all messages that are flagged as unread in the given folder.
+%
+%   Parameters:
+%   p_imapstore:        The imapstore object.
+%   p_foldername:       The folder name where the messages are stored.
+%
+%   Returns:
+%   unread_messages:    The messages that have been fetched.
+%   folder:             The folder object.
+
+%% Initialize return variables
+unread_messages = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_foldername))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Check connection
+
+if (~p_imapstore.isConnected())
+    disp('IMAPStore not connected, trying to reconnect...');
+    p_imapstore.connect();
+    if (~p_imapstore.isConnected())
+        warning('IMAPStore reconnect failed. Please check your configuration!');
+        return;
+    end
+end
+
+%% Get and open folder
+folder = p_imapstore.getFolder(p_foldername);
+folder.open(folder.READ_ONLY);
+
+%% Fetch all unread messages
+
+% there are difficulties accessing static class members of an inner java
+% class. This solution has been found at:
+% http://undocumentedmatlab.com/blog/accessing-internal-java-class-members
+search_flag = javax.mail.Flags(); % create search flag
+tmp = search_flag.getClass.getDeclaredClasses;
+seen_flag = tmp(1).getField('SEEN').get(1);
+clear tmp;
+search_flag.add(seen_flag);
+flagterm = javax.mail.search.FlagTerm(search_flag, false);
+unread_messages = folder.search(flagterm);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m b/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m
new file mode 100644
index 0000000000000000000000000000000000000000..9c46a96e3063358f014879adad0bd95e9d24b58c
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+mail/process_inline.m
@@ -0,0 +1,84 @@
+function [ processed_inline ] = process_inline( p_inline )
+%PROCESS_INLINE Searches the given inline text by the Iridium information and extracts them.
+%   Processes the given inline text and extracts the iridium information.
+%   
+%   Parameters:
+%   p_inline:           The inline text to be processed.
+%
+%   Returns:
+%   processed_inline:   Struct, containing the found information. Contains
+%                       false on fail.
+
+%% Initialize return variable
+processed_inline = false;
+
+%% Parameter check
+if (~ischar(p_inline))
+    warning([mfilename ': Wrong parameter given. Please check them carefully!']);
+    return;
+end
+
+%% Initialize values to extract
+utc = 'Time of Session (UTC): ';
+momsn = 'MOMSN: ';
+mtmsn = 'MTMSN: ';
+session_status = 'Session Status: ';
+message_size_in_bytes = 'Message Size (bytes): ';
+unit_location = 'Unit Location: ';
+cep_radius = 'CEPradius = ';
+
+%% Split message by lines
+lines = strsplit(p_inline, '\n');
+
+% find values and store them
+
+location = process_if_available(unit_location);
+if (~isa(location, 'logical'))
+    location = strsplit(strtrim(location));
+    iridium_lat = str2double(location{3});
+    iridium_lon = str2double(location{6});
+else
+    iridium_lat = false;
+    iridium_lon = false;
+end
+
+processed_inline = struct( ...
+    'TIMEOFSESSION',  process_if_available(utc), ...
+    'MOMSN',  process_if_available(momsn), ...
+    'MTMSN',  process_if_available(mtmsn), ...
+    'SESSIONSTATUS',  process_if_available(session_status), ...
+    'MESSAGESIZE',  process_if_available(message_size_in_bytes), ...
+    'IRIDIUM_LAT',  iridium_lat, ...
+    'IRIDIUM_LON',  iridium_lon, ...
+    'CEPRAD',  process_if_available(cep_radius) ...
+);
+
+
+%% Convert to usable matlab values
+if (~isa(processed_inline.TIMEOFSESSION, 'logical'))
+    processed_inline.TIMEOFSESSION = datevec(processed_inline.TIMEOFSESSION, 'ddd mmm dd HH:MM:SS yyyy');
+end
+if (~isa(processed_inline.MOMSN, 'logical'))
+    processed_inline.MOMSN = str2double(processed_inline.MOMSN);
+end
+if (~isa(processed_inline.MTMSN, 'logical'))
+    processed_inline.MTMSN = str2double(processed_inline.MTMSN);
+end
+if (~isa(processed_inline.MESSAGESIZE, 'logical'))
+    processed_inline.MESSAGESIZE = str2double(processed_inline.MESSAGESIZE);
+end
+if (~isa(processed_inline.CEPRAD, 'logical'))
+    processed_inline.CEPRAD = str2double(processed_inline.CEPRAD);
+end
+
+    function value = process_if_available(p_variable)
+        value = false;
+        try
+            value = lines{find(~cellfun(@isempty, strfind(lines, p_variable)))}(length(p_variable):end);
+        catch ex
+            return
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m
new file mode 100644
index 0000000000000000000000000000000000000000..ca0be1753be73ef1c424784398028dd7ac8a5857
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/devicedata.m
@@ -0,0 +1,65 @@
+function [device,data,status]=devicedata(devicedata)
+
+    % Decode types
+    dd = dec2bin(devicedata,8);
+    data = bin2dec(dd(5:8));
+    device = bin2dec(dd(1:4));
+    status = 1;
+    
+    % Set device and data type
+    switch (device)
+        case 1
+            % NEMO float
+            device = 'NEMO Standard      ';
+            switch (data)
+                case 0
+                    % Test/Startup data
+                    data = 'Startup Test Message';
+                case 1
+                    % Profile data
+                    data = 'Profile Message';
+                case 2
+                    % Report data
+                    data = 'MTM Report Message';
+                case 3
+                    % Recovery data
+                    data = 'Recovery Message';
+                case 4
+                    % File data
+                    data = 'File transmission Message';
+                case 5
+                    % Reserved profile data
+                    data = 'Reserved profile Message';
+                case 6
+                    % Filtered continous profile data 
+                    data = 'Filtered CTD data Message';
+
+                otherwise
+                    % Unknown data
+                    data = 'Unknown Message';
+                    %disp(['Unknown data type ('+num2str(dd)+')']);
+                    status = 0;
+            end
+            
+        case 2
+            % CTD Profiler
+            device = 'CTD Profiler/Logger';
+            switch (data)
+                case 1
+                    % Profile data
+                    data = 'Profile Message';
+               
+                otherwise
+                    % Unknown data
+                    data = 'Unknown Message';
+                    %disp(['Unknown data type ('+num2str(dd)+')']);
+                    status = 0;
+            end
+            
+        otherwise
+            % Unknown device type
+            device = 'Unknown';
+            status = 0;
+    end
+    
+end
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m
new file mode 100644
index 0000000000000000000000000000000000000000..91f67c3c706d187cca1a67d809a2e81437d0d6c3
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/phext_t2.m
@@ -0,0 +1,285 @@
+function [S,offset] = phext_t2(S,offset,k)
+
+% Decode different NEMO profile header extensions type 2 (mission
+% configuration)
+%
+%
+
+% Optimare Sensorsysteme, 24.05.2012 M.Busack
+
+switch (S.DS1_FORMAT(k))
+    
+    case 1
+        % Format 1 vom 8-Mar-2012
+        S.SERIAL = S.uD(offset+1)*256 + S.uD(offset);
+        S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2);
+        S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60;
+        S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6);
+        S.PRES_PROFILE = NaN; %orginal double
+        S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12);
+        S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14);
+        S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16);
+        S.DESCENT_MODE = S.uD(offset+18);
+        % Byte 19 not used
+        S.SPEED_DESCENT = NaN; %orginal double
+        S.DESCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28);
+        S.DESCENT_DEPTH_CONTROL = S.uD(offset+29);
+        S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30);
+        S.DESCENT_PISTON_ONTIME = S.uD(offset+32);
+        % Byte 33 not used
+        S.PRES_DESCENT_MARGIN = NaN; %orginal double
+        S.PRES_PARKING = NaN;   %orginal double
+        S.PRES_PARKING_HYSTERESE = S.uD(offset+42);
+        % Byte 43 not used
+        S.ATTENUATION_FACTOR = NaN; %orginal double
+        S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60;
+        S.PARKING_SAMPLES = S.uD(offset+52);
+        S.PARKING_DELAY = S.uD(offset+53);
+        S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54);
+        S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56);
+        S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58);
+        S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60);
+        S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62);
+        S.SPEED_SAG = NaN;  %orginal double
+        S.SAG_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_MODE = S.uD(offset+72);
+        % Byte 73 not used
+        S.SPEED_ASCENT = NaN;   %orginal double
+        S.ASCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82);
+        S.ASCENT_DEPTH_CONTROL = S.uD(offset+83);
+        S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84);
+        S.ASCENT_PISTON_ONTIME = S.uD(offset+86);
+        % Byte 87 not used
+        S.TEMP_ICEDETECT = NaN; %orginal double
+        S.PRES_ICEUP = S.uD(offset+93)*256 + S.uD(offset+92);   %until here verified with sbd 1
+        S.PRES_ICEDOWN = S.uD(offset+95)*256 + S.uD(offset+94);
+        S.PRES_ICEINTERVAL = S.uD(offset+96);
+        S.SUCCESSFUL_ASCENTS = S.uD(offset+97);
+        S.POST_UPCAST_SAMPLES = S.uD(offset+98);
+        S.POST_UPCAST_DELAY = S.uD(offset+99);
+        S.TIME_SURFACE_ATTEMP = S.uD(offset+100);
+        % Byte 101 not used
+        S.TIME_TX = (S.uD(offset+105)*16777216 + S.uD(offset+104)*65536 + S.uD(offset+103)*256 + S.uD(offset+102))/60;
+        S.MULTIMSG = [S.uD(offset+106) S.uD(offset+107) S.uD(offset+108) S.uD(offset+109)];
+        S.ARGOSID = S.uD(offset+110);
+        S.RAFOS_MODULEADR = S.uD(offset+111);
+        S.RECOVERY_START = S.uD(offset+113)*256 + S.uD(offset+112);
+        S.RECOVERY_TX = S.uD(offset+114);
+        % Byte 115 not used
+        S.RECOVERY_NOTX = S.uD(offset+117)*256 + S.uD(offset+116);
+        S.LOGMODE = S.uD(offset+118);
+        S.TESTMODE = S.uD(offset+119);
+        S.TIME_CO2WARMUP = (S.uD(offset+121)*256 + S.uD(offset+120))/60;
+        S.TIME_GPSFIX = (S.uD(offset+123)*256 + S.uD(offset+122))/60;
+        S.PRES_MAXALARM = S.uD(offset+125)*256 + S.uD(offset+124);
+        S.COUNT_MAXALARM = S.uD(offset+127)*256 + S.uD(offset+126);
+        S.CTDCP_FILTER = S.uD(offset+129)*256 + S.uD(offset+128);
+        S.PRES_CTDOFF_MAX = (S.sD(offset+131)*256 + S.uD(offset+130))/10;
+        S.PRES_CTDOFF_MIN = (S.sD(offset+133)*256 + S.uD(offset+132))/10;
+        S.PRES_CTDOFF_CHN = (S.uD(offset+135)*256 + S.uD(offset+134))/10;
+        S.PRES_PCUTOFF = (S.uD(offset+137)*256 + S.uD(offset+136))/10;
+        S.PRES_SAFETY = NaN;    %orginal double
+        S.STARTPROFILE = S.uD(offset+143)*256 + S.uD(offset+142);
+        S.ICEGPS_TIMEOUT = S.uD(offset+144);
+        S.ICEIRDM_TIMEOUT = S.uD(offset+145);
+        S.CONFIGTX = S.uD(offset+146);
+        S.RAFOS_ACTIVE = [S.uD(offset+147) S.uD(offset+148) S.uD(offset+149) S.uD(offset+150) S.uD(offset+151)];
+        S.RAFOS_STARTHH = [S.uD(offset+152) S.uD(offset+153) S.uD(offset+154) S.uD(offset+155) S.uD(offset+156)];
+        S.RAFOS_STARTMM = [S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160) S.uD(offset+161)];
+        S.RAFOS_ONMM = [S.uD(offset+163)*256+S.uD(offset+162) S.uD(offset+165)*256+S.uD(offset+164) ...
+                        S.uD(offset+167)*256+S.uD(offset+166) S.uD(offset+169)*256+S.uD(offset+168) ...
+                        S.uD(offset+171)*256+S.uD(offset+170)];
+        offset = offset + 172;
+        
+    case 2
+        % Format 2 vom 23-May-2012 
+        S.SERIAL = S.uD(offset+1)*256 + S.uD(offset);
+        S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2);
+        S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60;
+        S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6);
+        S.PRES_PROFILE = NaN; %orginal double
+        S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12);
+        S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14);
+        S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16);
+        S.DESCENT_MODE = S.uD(offset+18);
+        % Byte 19 not used
+        S.SPEED_DESCENT = NaN; %orginal double
+        S.DESCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28);
+        S.DESCENT_DEPTH_CONTROL = S.uD(offset+29);
+        S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30);
+        S.DESCENT_PISTON_ONTIME = S.uD(offset+32);
+        % Byte 33 not used
+        S.PRES_DESCENT_MARGIN = NaN; %orginal double
+        S.PRES_PARKING = NaN;   %orginal double
+        S.PRES_PARKING_HYSTERESE = S.uD(offset+42);
+        % Byte 43 not used
+        S.ATTENUATION_FACTOR = NaN; %orginal double
+        S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60;
+        S.PARKING_SAMPLES = S.uD(offset+52);
+        S.PARKING_DELAY = S.uD(offset+53);
+        S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54);
+        S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56);
+        S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58);
+        S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60);
+        S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62);
+        S.SPEED_SAG = NaN;  %orginal double
+        S.SAG_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_MODE = S.uD(offset+72);
+        % Byte 73 not used
+        S.SPEED_ASCENT = NaN;   %orginal double
+        S.ASCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82);
+        S.ASCENT_DEPTH_CONTROL = S.uD(offset+83);
+        S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84);
+        S.ASCENT_PISTON_ONTIME = S.uD(offset+86);
+        % Byte 87 not used
+        S.TEMP_ICEDETECT = NaN; %orginal double
+        S.PRES_ICEUP = S.uD(offset+93)*256 + S.uD(offset+92);   %until here verified with sbd 1
+        S.PRES_ICEDOWN = S.uD(offset+95)*256 + S.uD(offset+94);
+        S.PRES_ICEINTERVAL = S.uD(offset+96);
+        S.SUCCESSFUL_ASCENTS = S.uD(offset+97);
+        S.POST_UPCAST_SAMPLES = S.uD(offset+98);
+        S.POST_UPCAST_DELAY = S.uD(offset+99);
+        S.TIME_SURFACE_ATTEMP = S.uD(offset+100);
+        % Byte 101 not used
+        S.TIME_TX = (S.uD(offset+105)*16777216 + S.uD(offset+104)*65536 + S.uD(offset+103)*256 + S.uD(offset+102))/60;
+        S.MULTIMSG = [S.uD(offset+106) S.uD(offset+107) S.uD(offset+108) S.uD(offset+109)];
+        S.ARGOSID = S.uD(offset+110);
+        S.RAFOS_MODULEADR = S.uD(offset+111);
+        S.RECOVERY_START = S.uD(offset+113)*256 + S.uD(offset+112);
+        S.RECOVERY_TX = S.uD(offset+114);
+        % Byte 115 not used
+        S.RECOVERY_NOTX = S.uD(offset+117)*256 + S.uD(offset+116);
+        S.LOGMODE = S.uD(offset+118);
+        S.TESTMODE = S.uD(offset+119);
+        S.TIME_CO2WARMUP = (S.uD(offset+121)*256 + S.uD(offset+120))/60;
+        S.TIME_GPSFIX = (S.uD(offset+123)*256 + S.uD(offset+122))/60;
+        S.PRES_MAXALARM = S.uD(offset+125)*256 + S.uD(offset+124);
+        S.COUNT_MAXALARM = S.uD(offset+127)*256 + S.uD(offset+126);
+        S.CTDCP_FILTER = S.uD(offset+129)*256 + S.uD(offset+128);
+        S.PRES_CTDOFF_MAX = (S.sD(offset+131)*256 + S.uD(offset+130))/10;
+        S.PRES_CTDOFF_MIN = (S.sD(offset+133)*256 + S.uD(offset+132))/10;
+        S.PRES_CTDOFF_CHN = (S.uD(offset+135)*256 + S.uD(offset+134))/10;
+        S.PRES_PCUTOFF = (S.uD(offset+137)*256 + S.uD(offset+136))/10;
+        S.PRES_SAFETY = NaN;    %orginal double
+        S.STARTPROFILE = S.uD(offset+143)*256 + S.uD(offset+142);
+        S.ICEGPS_TIMEOUT = S.uD(offset+144);
+        S.ICEIRDM_TIMEOUT = S.uD(offset+145);
+        S.CONFIGTX = S.uD(offset+146);
+        % New 
+        S.AIRPUMP_MODE = S.uD(offset+147);
+        S.AIRPUMP_PRESSDIFF = S.uD(offset+149)*256 + S.uD(offset+148);
+        S.AIRPUMP_TIMEOUT = S.uD(offset+151)*256 + S.uD(offset+150);
+        S.MISSION_MOD = S.uD(offset+153)*256 + S.uD(offset+152);
+        S.TIME_CYCLE2 = S.uD(offset+155)*256 + S.uD(offset+154);
+        % Changed
+        S.RAFOS_ACTIVE = [S.uD(offset+156) S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160)];
+        S.RAFOS_STARTHH = [S.uD(offset+161) S.uD(offset+162) S.uD(offset+163) S.uD(offset+164) S.uD(offset+165)];
+        S.RAFOS_STARTMM = [S.uD(offset+166) S.uD(offset+167) S.uD(offset+168) S.uD(offset+169) S.uD(offset+170)];
+        % Byte 171 not used
+        S.RAFOS_ONMM = [S.uD(offset+173)*256+S.uD(offset+172) S.uD(offset+175)*256+S.uD(offset+174) ...
+                        S.uD(offset+177)*256+S.uD(offset+176) S.uD(offset+179)*256+S.uD(offset+178) ...
+                        S.uD(offset+181)*256+S.uD(offset+180)];
+        offset = offset + 182;
+       
+    case 3
+        % Format 3 vom 12-June-2012 (Ice detection temperature A01 change) 
+        S.SERIAL = S.uD(offset+1)*256 + S.uD(offset);
+        S.AIRPUMPTIME = S.uD(offset+3)*256 + S.uD(offset+2);
+        S.STARTUPTIME = (S.uD(offset+5)*256 + S.uD(offset+4))/60;
+        S.TIME_CYCLE = S.uD(offset+7)*256 + S.uD(offset+6);
+        S.PRES_PROFILE = NaN; %orginal double
+        S.PISTON_MIN = S.uD(offset+13)*256 + S.uD(offset+12);
+        S.PISTON_MAX = S.uD(offset+15)*256 + S.uD(offset+14);
+        S.STARTUPDELAY = S.uD(offset+17)*256 + S.uD(offset+16);
+        S.DESCENT_MODE = S.uD(offset+18);
+        % Byte 19 not used
+        S.SPEED_DESCENT = NaN; %orginal double
+        S.DESCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.DESCENT_PISTONINTERVAL_MIN = S.uD(offset+28);
+        S.DESCENT_DEPTH_CONTROL = S.uD(offset+29);
+        S.DESCENT_PISTONINTERVAL = S.uD(offset+31)*256 + S.uD(offset+30);
+        S.DESCENT_PISTON_ONTIME = S.uD(offset+32);
+        % Byte 33 not used
+        S.PRES_DESCENT_MARGIN = NaN; %orginal double
+        S.PRES_PARKING = NaN;   %orginal double
+        S.PRES_PARKING_HYSTERESE = S.uD(offset+42);
+        % Byte 43 not used
+        S.ATTENUATION_FACTOR = NaN; %orginal double
+        S.TIME_PARKSAMPLE = (S.uD(offset+51)*16777216 + S.uD(offset+50)*65536 + S.uD(offset+49)*256 + S.uD(offset+48))/60;
+        S.PARKING_SAMPLES = S.uD(offset+52);
+        S.PARKING_DELAY = S.uD(offset+53);
+        S.PRES_PARKINGHIGH = S.uD(offset+55)*256 + S.uD(offset+54);
+        S.SAFETYZONE_MARGIN = S.uD(offset+57)*256 + S.uD(offset+56);
+        S.SAFETYZONE_COUNTCHANGE = S.uD(offset+59)*256 + S.uD(offset+58);
+        S.SAFETYZONE_INCREMENT = S.uD(offset+61)*256 + S.uD(offset+60);
+        S.INITIAL_PARKCOUNT = S.uD(offset+63)*256 + S.uD(offset+62);
+        S.SPEED_SAG = NaN;  %orginal double
+        S.SAG_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_MODE = S.uD(offset+72);
+        % Byte 73 not used
+        S.SPEED_ASCENT = NaN;   %orginal double
+        S.ASCENT_TIMEOUTFACTOR = NaN;  %orginal double
+        S.ASCENT_PISTONINTERVAL_MIN = S.uD(offset+82);
+        S.ASCENT_DEPTH_CONTROL = S.uD(offset+83);
+        S.ASCENT_PISTONINTERVAL = S.uD(offset+85)*256 + S.uD(offset+84);
+        S.ASCENT_PISTON_ONTIME = S.uD(offset+86);
+        % Byte 87 not used
+        S.TEMP_ICEDETECT = NaN; %orginal double
+        S.ICE_DTMEDIAN = S.uD(offset+92);
+        S.ICE_DTENTRY = S.uD(offset+93);
+        S.SUCCESSFUL_ASCENTS = S.uD(offset+94);
+        S.POST_UPCAST_SAMPLES = S.uD(offset+95);
+        S.POST_UPCAST_DELAY = S.uD(offset+96);
+        S.TIME_SURFACE_ATTEMP = S.uD(offset+97);
+        S.TIME_TX = (S.uD(offset+101)*16777216 + S.uD(offset+100)*65536 + S.uD(offset+99)*256 + S.uD(offset+98))/60;
+        S.MULTIMSG = [S.uD(offset+102) S.uD(offset+103) S.uD(offset+104) S.uD(offset+105)];
+        S.ARGOSID = S.uD(offset+106);
+        S.RAFOS_MODULEADR = S.uD(offset+107);
+        S.RECOVERY_START = S.uD(offset+109)*256 + S.uD(offset+108);
+        S.RECOVERY_TX = S.uD(offset+110);
+        % Byte 111 not used
+        S.RECOVERY_NOTX = S.uD(offset+113)*256 + S.uD(offset+112);
+        S.LOGMODE = S.uD(offset+114);
+        S.TESTMODE = S.uD(offset+115);
+        S.TIME_CO2WARMUP = (S.uD(offset+117)*256 + S.uD(offset+116))/60;
+        S.TIME_GPSFIX = (S.uD(offset+119)*256 + S.uD(offset+118))/60;
+        S.PRES_MAXALARM = S.uD(offset+121)*256 + S.uD(offset+120);
+        S.COUNT_MAXALARM = S.uD(offset+123)*256 + S.uD(offset+122);
+        S.CTDCP_FILTER = S.uD(offset+125)*256 + S.uD(offset+124);
+        S.PRES_CTDOFF_MAX = (S.sD(offset+127)*256 + S.uD(offset+126))/10;
+        S.PRES_CTDOFF_MIN = (S.sD(offset+129)*256 + S.uD(offset+128))/10;
+        S.PRES_CTDOFF_CHN = (S.uD(offset+131)*256 + S.uD(offset+130))/10;
+        S.PRES_PCUTOFF = (S.uD(offset+133)*256 + S.uD(offset+132))/10;
+        S.PRES_SAFETY = NaN;    %orginal double
+        S.STARTPROFILE = S.uD(offset+139)*256 + S.uD(offset+138);
+        S.ICEGPS_TIMEOUT = S.uD(offset+140);
+        S.ICEIRDM_TIMEOUT = S.uD(offset+141);
+        S.CONFIGTX = S.uD(offset+142);
+        S.AIRPUMP_MODE = S.uD(offset+143);
+        S.AIRPUMP_PRESSDIFF = S.uD(offset+145)*256 + S.uD(offset+144);
+        S.AIRPUMP_TIMEOUT = S.uD(offset+147)*256 + S.uD(offset+146);
+        S.MISSION_MOD = S.uD(offset+149)*256 + S.uD(offset+148);
+        S.TIME_CYCLE2 = S.uD(offset+151)*256 + S.uD(offset+150);
+        % Changed
+        S.RAFOS_ACTIVE = [S.uD(offset+152) S.uD(offset+153) S.uD(offset+154) S.uD(offset+155) S.uD(offset+156)];
+        S.RAFOS_STARTHH = [S.uD(offset+157) S.uD(offset+158) S.uD(offset+159) S.uD(offset+160) S.uD(offset+161)];
+        S.RAFOS_STARTMM = [S.uD(offset+162) S.uD(offset+163) S.uD(offset+164) S.uD(offset+165) S.uD(offset+166)];
+        % Byte 167 not used
+        S.RAFOS_ONMM = [S.uD(offset+169)*256+S.uD(offset+168) S.uD(offset+171)*256+S.uD(offset+170) ...
+                        S.uD(offset+173)*256+S.uD(offset+172) S.uD(offset+175)*256+S.uD(offset+174) ...
+                        S.uD(offset+177)*256+S.uD(offset+176)];
+        offset = offset + 178;
+    otherwise
+        % Do nothing
+        
+end
+
+% function end
+end
+
+
+    
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m
new file mode 100644
index 0000000000000000000000000000000000000000..85885b5d45ce74fc77a741eed91f81ec8a28e4a9
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/press.m
@@ -0,0 +1,30 @@
+function p=press(DATA, off, cnt)
+
+    if cnt == 2
+        % Low resulution mode
+        p = DATA(off+1)*256 + DATA(off);
+        if p <= 32767
+            p = p/10;
+        else
+            p= (65535-p+1)/10*-1;
+        end
+    end
+    
+    if cnt == 4
+        % High resulution mode
+        p = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off);
+        if p <= 2147483647
+            p = p / 100;
+        else
+            p = (4294967295-p+1)/100*-1;
+        end
+    end
+
+
+% function p=cnv_press(cnt)
+% 
+%     if cnt <= 32767
+%         p=cnt/10;
+%     else
+%         p=(65535-cnt+1)/10*-1;
+%     end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m
new file mode 100644
index 0000000000000000000000000000000000000000..e382cfc5a6610535ad52318b04e34bc6e9f327ac
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/salt.m
@@ -0,0 +1,19 @@
+function S=salt(DATA, off, cnt)
+
+    if cnt == 2
+        % Low resulution mode
+        S = DATA(off+1)*256 + DATA(off);
+        S = S / 1000;
+    end
+    
+    if cnt == 4
+        % High resulution mode
+        S = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off);
+        S = S / 10000;
+    end
+
+
+% function S=cnv_salt(cnt)
+% 
+%         S=cnt/1000;
+%      
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m
new file mode 100644
index 0000000000000000000000000000000000000000..74fb89e9826bbe8e8933fccd52c3911ff85ca9a6
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/+cnv/temp.m
@@ -0,0 +1,31 @@
+function T=temp(DATA, off, cnt)
+
+    if cnt == 2
+        % Low resulution mode
+        T = DATA(off+1)*256 + DATA(off);
+        if T <= 62535
+            T = T/1000;
+        else
+            T= (65535-T+1)/1000*-1;
+        end
+    end
+    
+    if cnt == 4
+        % High resulution mode
+        T = DATA(off+3)*16777216 + DATA(off+2)*65536 + DATA(off+1)*256 + DATA(off);
+        if T <= 2147483647
+            T = T / 10000;
+        else
+            T = (4294967295-T+1)/10000*-1;
+        end
+    end
+
+
+
+% function T=cnv_temp(cnt)
+% 
+%     if cnt <= 62535
+%         T=cnt/1000;
+%     else
+%         T=(65535-cnt+1)/1000*-1;
+%     end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m
new file mode 100644
index 0000000000000000000000000000000000000000..f0aee1e893d22a5076ebabc41c1c4afaff23beda
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/decode_nemo.m
@@ -0,0 +1,316 @@
+function S = decode_nemo(S)
+
+% Decode different NEMO message types
+%
+%
+
+% Optimare Sensorsysteme, 22.10.2010 M.Busack
+
+
+% Enable(1)/Disable(0) 3830 Optode sensor
+funco2 = 1;
+
+switch S.DEVICE
+    
+    case 'CTD Profiler/Logger'
+        % Device type = CTD-Profiler/Logger
+        switch S.DATATYPE
+
+            case 'Profile Message'
+                % Read profile header
+                S.SERIAL = S.uD(4)*256 + S.uD(3);
+                S.PROFILE_NUMBER = S.uD(2)*256 + S.uD(1);
+                S.STATUS_UPCAST = nemo.optimare.stat2str(S.uD(5),'C');
+                % Startdate bytes 6,7,8 not used now
+                S.DS4_START = S.uD(9)*3600 + S.uD(10)*60 + S.uD(11);
+                S.BATT_CPU = S.uD(12) / 10;
+                S.BATT_PMP = S.uD(13) / 10;
+                S.PRES_TUBE_DEPTH = ( S.uD(14) / 10 ) * 68.9475;
+                % Enddate bytes 15,16,17 not used now
+                S.DS4_STOP = S.uD(18)*3600 + S.uD(19)*60 + S.uD(20);
+                S.BATT_CPU2 = S.uD(21) / 10;
+                S.BATT_PMP2 = S.uD(22) / 10;
+                S.PRES_TUBE_SURFACE = ( S.uD(23) / 10 ) * 69.9475;
+                %S.DATA_RECORDS_COMPLETE = S.uD(25)*256 + S.uD(24);
+                S.DATA_RECORDS1 = [0, 0, 0, S.uD(25)*256+S.uD(24), 0];
+                S.DATA_RECORDS2 = S.DATA_RECORDS1;
+                offset = 26;
+                % Read sample data
+                if S.DATA_RECORDS1(4) > 0
+                    for k=1:S.DATA_RECORDS1(4)
+                        S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, 2);
+                        S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+2, 2);
+                        S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+4, 2);
+                        offset = offset + 6;
+                    end
+                end
+
+        end
+
+        
+    case 'NEMO Standard      '
+        % Device type = NEMO float
+
+        switch S.DATATYPE
+
+            case 'Startup Test Message'
+                S.SERIAL = S.uD(2)*256 + S.uD(1);
+                dt = [S.uD(3)+2000,S.uD(4),S.uD(5),0,0,0];
+                S.SOFTWARE = datestr(dt);
+                dt = [S.uD(6)+2000,S.uD(7),S.uD(8),S.uD(9),S.uD(10),0];
+                S.RTC = datestr(dt);
+                S.PISTON_MAX = S.uD(12)*256 + S.uD(11);
+                S.PISTON_MIN = S.uD(14)*256 + S.uD(13);
+                S.BATT_CPU = S.uD(15) / 10;
+                S.BATT_PMP = S.uD(16) / 10;
+                S.BATT_EXT = S.uD(17) / 10;
+                S.PRES_TUBE_SURFACE = (S.uD(18) / 10) * 68.94757;
+                S.PRES_PROFILE = nemo.optimare.cnv.press(S.uD, 19, 2);
+                S.PRES_PARKING = nemo.optimare.cnv.press(S.uD, 21, 2);
+                S.TIME_TX = S.uD(24)*256 + S.uD(23);
+                S.TIME_PARKSAMPLE = S.uD(26)*256 + S.uD(25);
+                S.TIME_CYCLE = S.uD(28)*256 + S.uD(27);
+                S.DESCENT_MODE = S.uD(29);
+                S.RECOVERY_START = S.uD(32)*256 + S.uD(31);
+                S.DESCENT_SPEED = (S.uD(34)*256 + S.uD(33))/1000;
+                S.TEMP_ICEDETECT = (S.sD(36)*256 + S.uD(35))/1000;
+                S.RECOVERY_TX = S.uD(37);
+                S.RECOVERY_NOTX = S.uD(40)*256 + S.uD(39);
+                dt = [S.uD(41)+2000,S.uD(42),S.uD(43),S.uD(44),S.uD(45),0];
+                S.GPS_DATETIME = datestr(dt);
+                S.GPS_LAT = (S.sD(48)*256 + S.uD(47)) / 100;
+                S.GPS_LON = (S.sD(50)*256 + S.uD(49)) / 100;
+
+            case 'Profile Message'
+                % Read profile header (ab rev.224)
+                S.SERIAL = S.uD(4)*256 + S.uD(3);
+                S.PROFILE_NUMBER = S.uD(2)*256 + S.uD(1);
+                S.STATUS_UPCAST = nemo.optimare.stat2str(S.uD(5),'P');
+                % Byte 6 nicht benutzt
+                S.PROFILE_OLDER = S.uD(8)*256 + S.uD(7);
+                S.ERROR_MOTOR = S.uD(9);
+                S.ERROR_CTD = S.uD(10);
+                S.ERROR_OPTODE = S.uD(11);
+                S.ERROR_RAFOS = S.uD(12);
+                S.BATT_CPU = S.uD(13) / 10;
+                S.BATT_PMP = S.uD(14) / 10;
+                S.BATT_EXT = S.uD(15) / 10;
+                S.CURR_MOTOR = S.uD(16) / 100;
+                S.CURR_MOTOR_MEAN = S.uD(17) / 100;
+                if isnan(S.uD(18))
+                    S.CTD_MODE = NaN;
+                    S.CTD_RES = NaN;
+                else
+                    S.CTD_MODE = bitget(S.uD(18),1);
+                    S.CTD_RES = bitget(S.uD(18),2);
+                end
+                S.PRES_TUBE_SURFACE = S.uD(20)*256 + S.uD(19);
+                S.TEMP_TUBE_SURFACE = (S.sD(22)*256 + S.uD(21))/10;
+                S.PRES_TUBE_SURFACE2 = S.uD(24)*256 + S.uD(23);
+                S.TEMP_TUBE_SURFACE2 = (S.sD(26)*256 + S.uD(25))/10;
+                S.PRES_TUBE_DEPTH = S.uD(28)*256 + S.uD(27);
+                S.TEMP_TUBE_DEPTH = (S.sD(30)*256 + S.uD(29))/10;
+                S.TEMP_SHT = (S.sD(32)*256 + S.uD(31)) / 10;
+                S.HUM_SHT = (S.uD(34)*256 + S.uD(33)) / 10;
+                S.PRES_OFFSET = nemo.optimare.cnv.press(S.uD, 35, 2);
+                S.PRES_SURFACE = nemo.optimare.cnv.press(S.uD, 37, 2); 
+                S.PRES_PARKING = nemo.optimare.cnv.press(S.uD, 39, 2);
+                S.PRES_DEPTH = nemo.optimare.cnv.press(S.uD, 41, 2);
+                S.PRES_MAX = nemo.optimare.cnv.press(S.uD, 43, 2);
+                S.RECOVERY_START = S.uD(46)*256 + S.uD(45);
+                S.DATA_RECORDS1 = [S.uD(48)*256+S.uD(47), S.uD(50)*256+S.uD(49),...
+                    S.uD(52)*256+S.uD(51), S.uD(54)*256+S.uD(53), S.uD(56)*256+S.uD(55)];
+                S.PIST_SURFACE = S.uD(58)*256 + S.uD(57);
+                S.PIST_PARKING = S.uD(60)*256 + S.uD(59);
+                S.PIST_CALC = S.uD(62)*256 + S.uD(61);
+                S.PIST_DEPTH = S.uD(64)*256 + S.uD(63);
+                S.PIST_EOP = S.uD(66)*256 + S.uD(65);
+                S.MCNT_DESCENT = S.uD(70)*16777216 + S.uD(69)*65536 + S.uD(68)*256 + S.uD(67);
+                S.MCNT_PARKING = S.uD(74)*16777216 + S.uD(73)*65536 + S.uD(72)*256 + S.uD(71);
+                S.MCNT_SAG = S.uD(78)*16777216 + S.uD(77)*65536 + S.uD(76)*256 + S.uD(75);
+                S.MCNT_ASCENT = S.uD(82)*16777216 + S.uD(81)*65536 + S.uD(80)*256 + S.uD(79);
+                S.MCNT_ASCENT_END = S.uD(86)*16777216 + S.uD(85)*65536 + S.uD(84)*256 + S.uD(83);
+                S.MCNT_SURFACE = S.uD(90)*16777216 + S.uD(89)*65536 + S.uD(88)*256 + S.uD(87);
+                S.DATA_RECORDS2 = [S.uD(92)*256+S.uD(91), S.uD(94)*256+S.uD(93),...
+                    S.uD(96)*256+S.uD(95), S.uD(98)*256+S.uD(97), S.uD(100)*256+S.uD(99)];
+                % Verify data record quantity
+                for k = 1:5
+                    if ( (S.DATA_RECORDS1(k) == 0) && (S.DATA_RECORDS2(k) > 0) )
+                        q(k) = S.DATA_RECORDS2(k);
+                    else
+                        q(k) = S.DATA_RECORDS1(k);
+                    end
+                end
+                % Set offset in array and resulution increment
+                offset = 101;
+                if S.CTD_RES == 0
+                    ctda = 2;
+                else
+                    ctda = 4;
+                end
+                % Read profile data sets (ab Rev.224)
+                % Dataset 1 (profile header extensions)
+                if q(1) > 0
+                    for k=1:q(1)
+                        % This record header is always there
+                        S.DS1_TYPE(k) = S.uD(offset);
+                        S.DS1_FORMAT(k) = S.uD(offset+1);
+                        offset = offset + 2;
+                        switch(S.DS1_TYPE(k))
+                            case 1                      % Surface detect data only
+                                if S.DS1_FORMAT(k) == 1 % Default format
+                                    S.ICEGPS_TIME = S.uD(offset);
+                                    S.ICEIRDM_TIME = S.uD(offset+1);
+                                    offset = offset + 2;
+                                end
+                                % Paste additional formats for type 1 here
+                                
+                            case 2                      % Mission configuration data
+                                [S,offset] = nemo.optimare.cnv.phext_t2(S,offset,k);
+                                % Paste additional formats for type 2 here
+                                
+                            case 3                      % Airpump control data
+                                if S.DS1_FORMAT(k) == 1 % Default format
+                                    S.AIRPUMP_RUNTIME = S.uD(offset+1)*256 + S.uD(offset);
+                                    offset = offset + 2;
+                                end
+                                % Paste additional formats for type 3 here
+                                
+                            case 4                      % Ice detection temperature data
+                                if S.DS1_FORMAT(k) == 1 % Default format
+                                    S.ICE_TEMPMEDIAN = ( S.sD(offset+3)*16777216 + S.uD(offset+2)*65536 +...
+                                        S.uD(offset+1)*256 + S.uD(offset) ) / 10000;
+%                                     S.ICE_TEMPMEDIAN = ( S.uD(offset+3)*16777216 + S.uD(offset+2)*65536 +...
+%                                         S.uD(offset+1)*256 + S.uD(offset) ) / 10000;
+                                    offset = offset + 4;
+                                end
+                                % Paste additional formats for type 4 here
+                                
+                            % Paste additional types here
+                            
+                            otherwise
+                                % Do nothing
+                                
+                        end
+                    end         
+                end
+                % Dataset 2 (Parking data)
+                if q(2) > 0
+                    for k=1:q(2)
+                        S.DS2_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, ctda);
+                        S.DS2_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+ctda, ctda);
+                        S.DS2_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+2*ctda, ctda);
+                        offset = offset + 3*ctda;
+                    end
+                end
+                % Dataset 3 (Rafos data)
+                if q(3) > 0
+                    for k=1:q(3)
+                        S.DS3_STATUS(k) = S.uD(offset);
+                        S.DS3_DATE(k) = S.uD(offset+1)*372 + S.uD(offset+2)*31 + S.uD(offset+3);
+                        S.DS3_DATE_year(k) = S.uD(offset+1)*372 ;
+                        S.DS3_DATE_month(k) =  S.uD(offset+2);
+                        S.DS3_DATE_day(k) = S.uD(offset+3);
+
+                        S.DS3_TIME(k) = S.uD(offset+4)*3600 + S.uD(offset+5)*60 + S.uD(offset+6);
+                        for i=1:6
+                            S.DS3_RAFOSAMP(k,i) = S.uD(offset+6+i);
+                            S.DS3_RAFOSRANK(k,i) = (S.uD(offset+15+(i-1)*2)*256 + S.uD(offset+14+(i-1)*2))*0.3075;
+                        end
+                        % Evtl muss hier noch ein dummy byte eingefügt werden
+                        % (ist im 160 0 00 00 502 schon drin)
+                        % todo testen und in Firmware auch diesen datensatz
+                        % evtl. mit high resulution !
+                        S.DS3_PRES(k) = nemo.optimare.cnv.press(S.uD, offset+26, 2);
+                        S.DS3_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+28, 2);
+                        S.DS3_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+30, 2);
+                        offset = offset + 32;
+                    end
+                end
+                % Dataset 4 (Upcast profile data)
+                if q(4) > 0
+                    for k=1:q(4)
+                        S.DS4_TIME(k) = S.uD(offset)*3600 + S.uD(offset+1)*60 + S.uD(offset+2);
+                        S.DS4_FORMAT(k) = S.uD(offset+3);
+                        S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset+4, ctda);
+                        offset = offset + 4 + ctda;
+                        if S.CTD_MODE == 0
+                            % Discrete profile with full ctd data
+                            S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset, ctda);
+                            S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+ctda, ctda);
+                            offset = offset + ctda*2;
+                        else 
+                            % Cont. profile or NaN
+                            S.DS4_TEMP(k) = NaN;
+                            S.DS4_SAL(k) = NaN;
+                        end
+                        if ((funco2 > 0) || (S.DS4_FORMAT(k) > 0) )
+                            % Read additionaly sensor data
+                            [S,offset] = nemo.optimare.read_sensors(S, k, offset); 
+                        end
+                    end
+                end
+                % Dataset 5 (Extended surface data)
+                if q(5) > 0
+                    for k=1:q(5)
+                        dt = [S.uD(offset)+2000,S.uD(offset+1),S.uD(offset+2),S.uD(offset+3),S.uD(offset+4),S.uD(offset+5)];
+                        %S.DS5_RTC(k) = datestr(dt);
+                        S.DS5_RTC(k) = datenum(dt);
+                        dt = [S.uD(offset+6)+2000,S.uD(offset+7),S.uD(offset+8),S.uD(offset+9),S.uD(offset+10),S.uD(offset+11)];
+                        %S.DS5_GPS(k) = datestr(dt);
+                        S.DS5_GPS(k) = datenum(dt);
+                        S.DS5_LAT(k) = (S.sD(offset+15)*16777216 + S.uD(offset+14)*65536 + S.uD(offset+13)*256 + S.uD(offset+12)) / 10000000;
+                        S.DS5_LON(k) = (S.sD(offset+19)*16777216 + S.uD(offset+18)*65536 + S.uD(offset+17)*256 + S.uD(offset+16)) / 10000000;
+                        offset = offset + 20;
+                    end
+                end
+
+            case 'MTM Report Message'
+
+
+
+            case 'Recovery Message'
+
+
+
+            case 'File transmission message'
+
+
+            case 'Filtered CTD data Message'
+                % Read continous profile data header (filtered)
+                S.SERIAL = S.uD(2)*256 + S.uD(1);            
+                S.PROFILE_NUMBER = S.uD(4)*256 + S.uD(3);
+                S.DS4_START = S.uD(5)*3600 + S.uD(6)*60 + S.uD(7);
+                S.DS4_STOP = S.uD(8)*3600 + S.uD(9)*60 + S.uD(10);
+                S.DATA_RECORDS_COMPLETE = S.uD(12)*256 + S.uD(11);
+                S.DATA_RECORDS1 = [0, 0, 0, S.uD(14)*256+S.uD(13), 0];
+                S.DATA_RECORDS2 = S.DATA_RECORDS1;
+                S.CTD_RES = bitget(S.uD(15),2);
+                offset = 16;
+                if S.CTD_RES == 0
+                    ctda = 2;
+                else
+                    ctda = 4;
+                end
+                % Read sample data
+                if S.DATA_RECORDS1(4) > 0
+                    for k=1:S.DATA_RECORDS1(4)
+                        S.DS4_PRES(k) = nemo.optimare.cnv.press(S.uD, offset, ctda);
+                        S.DS4_TEMP(k) = nemo.optimare.cnv.temp(S.uD, offset+ctda, ctda);
+                        S.DS4_SAL(k) = nemo.optimare.cnv.salt(S.uD, offset+2*ctda, ctda);
+                        offset = offset + 3*ctda;
+                    end
+                end
+
+        end
+
+    otherwise
+        % Unknown device type
+        
+end
+
+% function end
+end
+
+
+    
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m
new file mode 100644
index 0000000000000000000000000000000000000000..3f4688afb28a6a8813813549b87f289b00121b92
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/process_moms.m
@@ -0,0 +1,340 @@
+function [S_all,S,SBD] = process_moms(filepath, imei)
+
+% Find and indentify mobile originated messages (mom) from Iridium SBD
+% Transmitter. 
+%
+%
+% ATTENTION: Include path to repository /oze/functions !
+
+% Optimare Sensorsysteme, 21.10.2010 M.Busack
+
+% compatibility fix, LP, 26.04.17:
+S_all = struct();
+
+% Get version from SVN
+S.VERSION = '$Id: SBD2PROF.m 999 2012-06-18 10:00:00Z michael $';
+%S_all.empty=[];
+%clear all;
+
+% % Set filepath or get path via file dialog 
+% filepath='/home/michael/src/matlab/sbd2prof/sbd';
+% filepath=uigetdir;
+
+% Added support for searching specific imei
+% Get all files from directory with .sbd extension
+if (~exist('imei', 'var'))
+    D = dir(fullfile(filepath,'*.sbd'));
+else
+    D = dir(fullfile(filepath, ['*' char(imei) '*.sbd']));
+end
+
+filelist=char(D.name);
+
+% Create figure
+%figure;
+%hold on;
+
+% bugfix: Output argument "SBD" (and maybe others) not assigned during
+% call to "nemo.optimare.process_moms".
+% LP, 27.04.17
+if (length(D) == 0)
+    SBD = [];
+end
+
+% Read files
+for i=1:length(D)
+    % Open file
+    infile=fullfile(filepath,filelist(i,:));
+    % Read Message
+    SBD(i) = nemo.optimare.readSBDmsg(infile);             
+end
+
+
+% Init processing variables
+state = 'Readpart';    
+findex = 0;         % File index
+grpflag = 0;        % Message group flag, set to 1 if startmsg was found. Set to 0 at end of group.
+msgcnt = 1;         % Message counter
+ofid = 0;           % No output file
+exit = 0;
+
+% Init overall energy variables
+pnr = 0;
+E.ENERGYCOMPLETE = 0;
+
+% Process messages
+while exit == 0
+    
+    %state
+    switch state
+        
+        case 'Readpart'
+            % Increment file counter index
+            findex = findex + 1;
+            if findex <= length(D)
+                % We have at least one file to read.
+                % Read SBD message
+                filename = fullfile(filepath,filelist(findex,:));
+                %SBD(findex) = nemo.optimare.readSBDmsg(filename);
+                % Check if we have a valid message type
+                if SBD(findex).status == 1
+                    % Yes, this is a known device/data message
+                    state = 'Getpart';
+                else
+                    % No, try again with next message
+                    disp('Error: File has unknown device/data type');
+                    state = 'Readpart';
+                end
+            else
+                % We reached the end of filelist.
+                % Before end, check if there is a group pending.
+                if grpflag == 1
+                    % A message group is pending, finish this first.
+                    state = 'Endgroup';
+                else
+                    % Everything if finished, end function.
+                    exit = 1;
+                end
+            end
+            
+        case 'Getpart'
+            % Decodable message was found
+            if SBD(findex).elementary_num == 1
+                % This is the first message of a group
+                if grpflag == 0
+                    % Last group was finished successfully.
+                    % => Everything is ok.
+                    state = 'Startgroup';
+                else
+                    % We have an incomplete group pending.
+                    % => Finish this group before starting the next one.
+                    % Because findex in incremented after process,
+                    % decrement here to prevent skipping of a file.
+                    findex = findex - 1; 
+                    state = 'Endgroup';
+                end
+            else
+                % This is not the first message of a group
+                if grpflag == 0
+                    % Group was not started before.
+                    % => This can happen if the first message is missing
+                    state = 'Startgroup';
+                else
+                    % Start of group was received before. 
+                    % => Everything is ok.
+                    % Exception: Last message of group before is missing
+                    % AND first message of next group is missing also !
+                    % TBD!!
+                    state = 'Checkpart';
+                end
+            end
+                
+        case 'Startgroup'
+            % Start of message group found.
+            % First, clear data from group before.
+            clear S;
+            grpflag = 1;
+            % Init device/data type
+            S.DEVICE = SBD(findex).device;
+            S.DATATYPE = SBD(findex).data;
+            % Set message counter
+            msgcnt = SBD(findex).elementary_num;
+            % Init buffer
+            l = SBD(findex).total_num * SBD(findex).ldata + 10;
+            l = l * 1.3;
+            l = ceil(l);              % OB:  added ceil, as ones requires interger parameter
+            S.uD=ones(1,l)*NaN;    
+            S.sD=S.uD;
+            % Offset for uD/sD Array. All messages must have the same
+            % datasize here (expect the last one).
+            S.DATAPAYLOAD = SBD(findex).ldata;
+            % Create output file name with .out extension
+            filename = filelist(findex,:);
+            l = length(filename);
+            filename(l) = 't';
+            filename(l-1) = 'u';
+            filename(l-2) = 'o';
+            % Open file for output
+            outfile = fullfile(filepath,filename);
+            %ofid = fopen(outfile,'w');
+            % Next state
+            state = 'Checkpart';
+
+        case 'Checkpart'
+            % Check for message consitence
+            if SBD(findex).elementary_num == msgcnt
+                % Expected message number is ok
+                state = 'Writepart';
+            else
+                % Message number read is not the expected one
+                % This can happen if one message/file is missing
+                %fprintf(ofid,'\r\nFilename: %s  Device: %s  Data type: %s  Message: %3d of %3d, not found',...
+                %    filelist(findex,:), SBD(findex).device, SBD(findex).data, msgcnt, SBD(findex).total_num);
+                % What to do ?
+                if msgcnt < SBD(findex).elementary_num
+                    % Expected message number is smaller than received one.
+                    % This can happen if a group file in the middle is missing. 
+                    % Try again expecting next message.
+                    msgcnt = msgcnt + 1;
+                else
+                    % Expected message number is larger than received one.
+                    state = 'Endgroup';
+                    
+                    % Ist das richtig ? :
+                    % This can happen if this is the next group, already.
+                    % Maybe the last message from the group before and the
+                    % first message of the next group are missing.
+                end
+            end
+            
+        case 'Writepart'
+            % Write header information to file
+            %fprintf(ofid,'\r\nFilename: %s  Device: %s  Data type: %s  Message: %3d of %3d, %3d bytes of data',...
+            %    filelist(findex,:), SBD(findex).device, SBD(findex).data, SBD(findex).elementary_num,...
+            %    SBD(findex).total_num, SBD(findex).ldata);
+            % Copy data to buffer
+            offset = (SBD(findex).elementary_num-1)*S.DATAPAYLOAD;
+            i = offset+1:offset+SBD(findex).ldata;
+            S.uD(i) = SBD(findex).udata;
+            S.sD(i) = SBD(findex).sdata;
+            % Check if message is last in set
+            if SBD(findex).elementary_num == SBD(findex).total_num
+                % Message is last one in set, end of group reached.
+                state = 'Endgroup';
+            else
+                % Not the last message, read next one.
+                msgcnt = msgcnt + 1;
+                state = 'Readpart';
+            end
+            
+        case 'Endgroup'
+            % Finish group
+            % Wir auch aufgerufen, wenn die leltzte msg fehlt !
+            % Close file.
+            %fprintf(ofid,'\r\nNo more messages to read.');
+            %fclose(ofid);
+            % Set flag to 'group closed successfully'          
+            grpflag = 0;
+            state = 'Process';
+            
+        case 'Process'
+            try
+            % Process the received group
+            % First, decode data
+            S = nemo.optimare.decode_nemo(S);
+            % Write data to output file
+            %write_NEMO(S,outfile);
+            catch ex
+            end
+            if findex > length(D)
+                exit = 1;
+            else
+                %state = 'Energycalculation';    % with energy calculation
+                state = 'Readpart';             % without any calculations
+            end
+            expr=['S_all.S_' num2str(findex) '=S;']; 
+            eval(expr);
+            
+        case 'Energycalculation'
+            % Perform energy calculation from mission runtime and piston
+            % movements
+            if isfield(S,'DATA_RECORDS1'),
+                if S.DATA_RECORDS1(4) > 0
+                    %plot(S.DS4_PRES+findex);     % Datset 4 pressure
+                    %plot(S.PROFILE_NUMBER, S.DS4_LICH1+S.DS4_LICH2+S.DS4_LICH3, '.-b');
+                    %plot(S.PROFILE_NUMBER,S.DS4_LICH1,'.-r',...
+                    %    S.PROFILE_NUMBER,S.DS4_LICH2,'.-g',...
+                    %    S.PROFILE_NUMBER,S.DS4_LICH3,'.-b');
+
+                end
+            end
+            % Calculate overall energy (works only if all data from same float)
+            if isfield(S,'PROFILE_NUMBER'),
+                pnr = pnr + 1;
+                E.VALID(pnr) = 1;
+                E.PROFNR(pnr) = S.PROFILE_NUMBER;
+                
+                if ( (isfield(S,'PIST_SURFACE')) & (isfield(S,'PIST_PARKING')) )
+                    E.PISTON_DESCENT(pnr) = S.PIST_PARKING - S.PIST_SURFACE;
+                else
+                    E.VALID(pnr) = 0;
+                end
+                if ( (isfield(S,'PIST_DEPTH')) & (isfield(S,'PIST_EOP')) & (E.VALID(pnr) == 1) )
+                    E.PISTON_ASCENT(pnr) = S.PIST_DEPTH - S.PIST_EOP;
+                else
+                    E.VALID(pnr) = 0;
+                end
+                if ( (isfield(S,'CURR_MOTOR_MEAN')) & (E.VALID(pnr) == 1) )
+                    E.MOTORCURRENT(pnr) = S.CURR_MOTOR_MEAN;
+                else
+                    E.VALID(pnr) = 0;
+                end
+                if E.VALID(pnr) == 1
+                    E.ENERGY(pnr) = (E.PISTON_DESCENT(pnr) + E.PISTON_ASCENT(pnr)) * E.MOTORCURRENT(pnr);
+                    E.ENERGYCOMPLETE = E.ENERGYCOMPLETE + E.ENERGY(pnr);
+                end
+            end
+            % Start again
+            state = 'Readpart';
+            
+        otherwise
+            % End loop
+            disp('State machine error');
+            exit = 1;
+    end
+end
+ 
+%hold off;
+
+%whos;
+
+% 
+%        
+%                 % This is the first message of a set.
+%                 if ( (findex > 1) & (msgcnt < SBD(findex-1).total_num) )
+%                     % Only if this is NOT the first file from the list
+%                     % and the expected elementray number (msgcnt) is
+%                     % smaller than the total number of this set.
+%                     % This can happen if the last set was not received 
+%                     % completly (one or more messages missing at the
+%                     % end)
+%                     % Todo: Search for any of the missing files.
+%                     state = 'Checkpart';
+%                         
+%                     % Note: msgcnt is equal to total number if last
+%                     % set was completly received. So, we have a set with 
+%                     % at least the last file missing.
+%                     % Use header informations from last message.
+%                     %SBD(findex).device = SBD(findex-1).device;
+%                     %SBD(findex).data = SBD(findex-1).data;
+%                     %SBD(findex).total_num = SBD(findex-1).total_num;
+%                     %msgcnt = msgcnt + 1;
+%                     %state = 'Checkpart';
+%                         
+%                 else
+%                     % Start of message set (i.e. a profile) found
+%                     if ofid > 0
+%                         % Close open output file
+%                         % This can happen, if last file of message set is
+%                         % missing
+%                         fclose(ofid);
+%                     end
+%                     % Start of new message set
+%                     state = 'Startpart';
+%                 end
+%             else 
+%                 % Message is not the first one
+%                 if ofid == 0
+%                     % No output file was created before.
+%                     % This can happen, if first message of set is missing
+%                     state = 'Startpart';
+%                     msgcnt = SBD(findex).elementary_num;
+%                 else
+%                     % Output file present, regular writing of data
+%                     state = 'Checkpart';
+%                 end
+%             end
+
+
+
+%end
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m
new file mode 100644
index 0000000000000000000000000000000000000000..f5d6bae06fb403822ee0f7c72ac0c13177739feb
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/readSBDmsg.m
@@ -0,0 +1,55 @@
+function sbdmsg = readSBDmsg(infi)
+
+% Read data from SBD file 
+%
+
+% Optimare Sensorsysteme, 21.10.2010 M.Busack
+
+% VERSION = '$Id: CTD2awi.m 701 2010-10-21 10:00:00Z michael $';
+
+
+
+% Open file for reading
+fid=fopen(infi,'r');
+
+% Read first header byte : crc_status
+sbdmsg.crc_status = fread(fid,1,'uchar');
+
+% Read next : checksum
+sbdmsg.checksum = fread(fid,1,'uint16');
+
+% Read next : mtmsn
+sbdmsg.mtmsn = fread(fid,1,'uchar');
+
+% Read next : elementry message number
+sbdmsg.elementary_num = fread(fid,1,'uchar');
+
+% Read next : total message number
+sbdmsg.total_num = fread(fid,1,'uchar');
+
+% Read next : device/data type
+device_data = fread(fid,1,'uchar');
+[sbdmsg.device,sbdmsg.data,sbdmsg.status] = nemo.optimare.cnv.devicedata(device_data);
+
+% Read next : profile number LSB
+sbdmsg.profilenumber_lsb = fread(fid,1,'uchar');
+
+% Read rest of file and store to 'udata' and 'sdata' array
+offset = ftell(fid);
+sbdmsg.udata = fread(fid,inf,'uchar');
+fseek(fid,offset,'bof');   %  OB   what is this good for?
+sbdmsg.sdata = fread(fid,inf,'schar');
+sbdmsg.ldata = ftell(fid) - offset;
+
+sbdmsg.sbdfilename=infi;
+[~, filename, ~] = fileparts(infi);
+splitted_file = strsplit(filename, '_');
+sbdmsg.imei = str2num(splitted_file{1});
+sbdmsg.momsn = str2num(splitted_file{2});
+
+
+% Close file
+fclose(fid);
+
+
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m
new file mode 100644
index 0000000000000000000000000000000000000000..3e31b3906a51eb943a14de7e17679a3e4d63280b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/read_sensors.m
@@ -0,0 +1,75 @@
+function [S,off] = read_sensors(S, i, off)
+
+% Read optinal data from additional sensors
+%
+%
+
+% Optimare Sensorsysteme, 26.10.2010 M.Busack
+
+o2type = 3830;
+%o2type = 4330;
+
+% Format
+%sen = uint8(S.uD(off));
+%off = off + 2;
+sen = uint8(S.DS4_FORMAT(i));
+
+% O2 Optode data
+if bitget(sen,2) == 1
+    if o2type == 4330
+        % 4330 Optode
+        S.DS4_O2CONC(i) = (S.uD(off+3)*16777216 + S.uD(off+2)*65536 + S.uD(off+1)*256 + S.uD(off)) / 1000;
+        S.DS4_O2SATU(i) = (S.uD(off+7)*16777216 + S.uD(off+6)*65536 + S.uD(off+5)*256 + S.uD(off+4)) / 1000;
+        S.DS4_O2TEMP(i) = (S.uD(off+11)*16777216 + S.uD(off+10)*65536 + S.uD(off+9)*256 + S.uD(off+8)) / 1000;
+        S.DS4_O2CALP(i) = (S.uD(off+15)*16777216 + S.uD(off+14)*65536 + S.uD(off+13)*256 + S.uD(off+12)) / 1000;
+        S.DS4_O2TCP(i) = (S.uD(off+19)*16777216 + S.uD(off+18)*65536 + S.uD(off+17)*256 + S.uD(off+16)) / 1000;
+        off = off + 20;
+        %disp(['Read_sensors offset ',num2str(off)]);
+    end
+    if o2type == 3830
+        % 3830 Optode
+        S.DS4_O2CONC(i) = ( (S.uD(off+1)*256 + S.uD(off)) / 100 ) - 10;
+        S.DS4_O2SATU(i) = ( (S.uD(off+3)*256 + S.uD(off+2)) / 100 ) - 10;
+        S.DS4_O2TEMP(i) = ( (S.uD(off+5)*256 + S.uD(off+4)) / 100 ) - 10;
+        off = off + 6;
+        if bitget(sen,1) == 1
+            % Raw data format
+            S.DS4_O2DPHASE(i) = (S.uD(off+1)*256 + S.uD(off)) / 100;
+            S.DS4_O2BPHASE(i) = (S.uD(off+3)*256 + S.uD(off+2)) / 100;
+            S.DS4_O2RPHASE(i) = (S.uD(off+5)*256 + S.uD(off+4)) / 100;
+            S.DS4_O2BAMP(i) = (S.uD(off+9)*16777216 + S.uD(off+8)*65536 + S.uD(off+7)*256 + S.uD(off+6)) / 10;
+            S.DS4_O2BPOT(i) = (S.uD(off+11)*256 + S.uD(off+10)) / 100;
+            S.DS4_O2RAMP(i) = (S.uD(off+15)*16777216 + S.uD(off+14)*65536 + S.uD(off+13)*256 + S.uD(off+12)) / 10;
+            S.DS4_O2RAWTEMP(i) = (S.sD(off+19)*16777216 + S.uD(off+18)*65536 + S.uD(off+17)*256 + S.uD(off+16)) / 100;
+            off = off + 20;
+        end
+    end
+end
+
+
+% CO2 sensor data
+if bitget(sen,3) == 1
+    dt = [S.uD(off)+2000,S.uD(off+1),S.uD(off+2),S.uD(off+3),S.uD(off+4),S.uD(off+5)];
+    %S.DS4_CO2DATETIME(i) = datestr(dt);
+    S.DS4_CO2DATETIME(i) = datenum(dt);
+    S.DS4_CO2PNDIR(i) = S.uD(off+9)*16777216 + S.uD(off+8)*65536 + S.uD(off+7)*256 + S.uD(off+6);
+    S.DS4_CO2PIN(i) = S.uD(off+13)*16777216 + S.uD(off+12)*65536 + S.uD(off+11)*256 + S.uD(off+10);
+    S.DS4_CO2SRAW(i) = S.uD(off+15)*256 + S.uD(off+14);
+    S.DS4_CO2SREF(i) = S.uD(off+17)*256 + S.uD(off+16);
+    S.DS4_CO2TSEN(i) = S.uD(off+19)*256 + S.uD(off+18);
+    S.DS4_CO2PCO2(i) = S.sD(off+23)*16777216 + S.uD(off+22)*65536 + S.uD(off+21)*256 + S.uD(off+20);
+    S.DS4_CO2TCTRL(i) = S.uD(off+25)*256 + S.uD(off+24);
+    S.DS4_CO2TGAS(i) = S.uD(off+27)*256 + S.uD(off+26);
+    S.DS4_CO2RHGAS(i) = S.uD(off+31)*16777216 + S.uD(off+30)*65536 + S.uD(off+29)*256 + S.uD(off+28);
+    off = off + 32;
+end
+
+
+% Lightsensor data
+if bitget(sen,4) == 1
+    S.DS4_LICH1(i) = S.uD(off+1)*256 + S.uD(off);
+    S.DS4_LICH2(i) = S.uD(off+3)*256 + S.uD(off+2);
+    S.DS4_LICH3(i) = S.uD(off+5)*256 + S.uD(off+4);
+    off = off + 6;
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m b/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m
new file mode 100644
index 0000000000000000000000000000000000000000..674c10265d3b6f31a0aa6d91bcea956ab783b878
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+optimare/stat2str.m
@@ -0,0 +1,70 @@
+function txt = stat2str(status, type)
+
+% Convert number of status to string
+%
+%
+
+% Optimare Sensorsysteme, 22.10.2010 M.Busack
+
+if type == 'R'
+    switch (status)
+        
+        case 0
+            txt = sprintf('OFF          ');
+            
+        case 1
+            txt = sprintf('ON           ');
+            
+        case 2
+            txt = sprintf('DONE         ');
+            
+        case 3
+            txt = sprintf('OVERLAP ERROR');
+            
+        case 4
+            txt = sprintf('ENDON ERROR  ');
+            
+        case 5
+            txt = sprintf('COM ERROR    ');
+            
+        otherwise
+            txt = sprintf('UNKNOWN      ');     
+    end
+    
+end
+
+if type == 'P'
+    switch (status)
+
+        case 0
+            txt = sprintf('BUSY         ');
+
+        case 1
+            txt = sprintf('DONE         ');
+
+        case 2
+            txt = sprintf('TIMEOUT      ');
+
+        case 3
+            txt = sprintf('ICE DETECTION');
+
+        otherwise
+            txt = sprintf('UNKNOWN      ');
+    end
+    
+end
+
+if type == 'C'
+    switch (status)
+
+        case 0
+            txt = sprintf('DONE         ');
+
+        case 1
+            txt = sprintf('CTD FAILURE  ');
+
+        otherwise
+            txt = sprintf('UNKNOWN      ');
+    end
+    
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m b/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m
new file mode 100644
index 0000000000000000000000000000000000000000..30697ef351bcd354b05bcfa25856d220310aca28
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/+templates/create_south.m
@@ -0,0 +1,157 @@
+function [ figure_id ] = create_south( p_config )
+%CREATE_SOUTH Creates a template plot that can be used for a variety of plots that require Weddell Sea as location.
+%
+%   Returns:
+%   figure_id:      Figure handle of the template.
+%
+%   Parameters:
+%   p_config:       Global configuration file, load it using nemo.load.config().
+
+
+%% Initialize return variable
+figure_id = false;
+
+%% Create template
+
+% load additional files from disk
+additional_content = nemo.load.additional_content(p_config);
+
+% store loaded data (should be changed on long term thought)
+Latitudes = additional_content.antarctic_latitudes;
+Longitudes = additional_content.antarctic_longitudes;
+ASI_Ice_Concentration = additional_content.antarctic_ice_concentration;
+Z = additional_content.etopo_z;
+refvec = additional_content.etopo_refvec;
+moorings = additional_content.moorings;
+
+clear additional_content
+
+% initialize figure
+figure_tmp = figure('Visible', 'off');
+% prepare contour variables
+[C1000,~] = contourm(Z,refvec,[-1000, -1000]);
+[C2000,~] = contourm(Z,refvec,[-2000, -2000]);
+[C3000,~] = contourm(Z,refvec,[-3000, -3000]);
+%[C3500,~] = contourm(Z,refvec,[-3500 -3500]);
+%[C4000,~] = contourm(Z,refvec,[-4000 -4000]);
+close(figure_tmp);
+clear figure_tmp Z refvec
+
+%figure_id = figure();
+figure_id = figure('Visible', 'Off');
+
+%set(figure_id,'color',[1 1 1],'Position',get(0,'ScreenSize'));
+%set(figure_id,'color',[1 1 1],'Position',[0, 0, 4000, 2250]);
+worldmap(p_config.PLOT_OVERVIEW_SOUTH.latitude_limit, ...
+    p_config.PLOT_OVERVIEW_SOUTH.longitude_limit);
+load coast
+plotm(lat,long,'k');
+
+geoshow(Latitudes, Longitudes, ASI_Ice_Concentration, 'DisplayType','surface');
+contourcmap('gray',10,'colorbar','off','location','horizontal', 'Visible', 'off');
+nValues = 128;  %# The number of unique values in the colormap
+%map = [linspace(0.5,1,nValues)' linspace(0.7,1,nValues)' ones(nValues,1)];  %'# 128-by-3 colormap
+%colormap(map);
+cmocean('ice', 'pivot', '-1');
+
+ax_1000=plotm(C1000(2,:),C1000(1,:),'color',[0.2 0.6 .8]);
+ax_2000=plotm(C2000(2,:),C2000(1,:),'color',[0 0 1]);
+ax_3000= plotm(C3000(2,:),C3000(1,:),'color',[0.2 .4 0.8]);
+
+%ax_3500= plotm(C3500(2,:),C3500(1,:),'color',[0.2 .2 0.8]);
+%ax_4000= plotm(C4000(2,:),C4000(1,:),'color',[0.2 .0 0.8]);
+
+%% make legend
+%opengl hardware; % can only be used on pc with dedicated graphics
+
+ax_deploy=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.deploy_marker,'markersize',p_config.PLOT_STYLE_SOUTH.deploy_markersize,'color', p_config.PLOT_STYLE_SOUTH.deploy_color,'linestyle','none');
+ax_gps=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.gps_marker,'markersize',p_config.PLOT_STYLE_SOUTH.gps_markersize,'color',p_config.PLOT_STYLE_SOUTH.gps_color,'MarkerFaceColor', p_config.PLOT_STYLE_SOUTH.gps_color,'linestyle','none');
+ax_gps_interpolated=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.gps_marker,'markersize',p_config.PLOT_STYLE_SOUTH.gps_markersize,'color',p_config.PLOT_STYLE_SOUTH.gps_color_interpolated,'MarkerFaceColor', p_config.PLOT_STYLE_SOUTH.gps_color_interpolated,'linestyle','none');
+ax_last_transmittion=plot3m(0,0,-1,'marker',p_config.PLOT_STYLE_SOUTH.last_transmittion_marker,'markersize',p_config.PLOT_STYLE_SOUTH.last_transmittion_markersize,'color',p_config.PLOT_STYLE_SOUTH.last_transmittion_color,'MarkerFaceColor',p_config.PLOT_STYLE_SOUTH.last_transmittion_color,'linestyle','none');
+
+
+legend([ax_deploy,ax_gps,ax_gps_interpolated,ax_last_transmittion,ax_1000,ax_2000,ax_3000],'Deployment positions','Best guess positions', 'Best guess positions (interpolated)', 'Last known position','-1000m','-2000m','-3000m');
+%legend([ax_deploy,ax_gps,ax_gps_interpolated,ax_last_transmittion,ax_3000,ax_3500,ax_4000],'Deployment positions','Best guess positions', 'Best guess positions (interpolated)', 'Last known position','-3000m','-3500m','-4000m');
+
+% cb=colorbar('southoutside');
+% ylabel(cb, 'Sea ice concentration in %');
+
+%% create title
+text_figure_title = title('', 'position', [4.367541205137968 -5154159.006427274 99.99999999999999]);
+set(text_figure_title,'interpreter','none','fontsize',14);
+
+%% plot soso
+
+earthradius = 3.4401e+003; % nm
+
+
+if p_config.PLOT_OVERVIEW_SOUTH.show_moorings == 1
+    
+    if ~iscell(moorings)
+        warning([mfilename ': Error loading moorings, skipping...']);
+    else
+        for o_moorings = 1:length(moorings)
+            lat = moorings{o_moorings}.lat;
+            lon = moorings{o_moorings}.lon;
+            detection_radius = moorings{o_moorings}.detection_radius;
+            if (~moorings{o_moorings}.hide_mooring)
+                switch (p_config.PLOT_STYLE_SOUTH.moorings_display_format)
+                    case 1
+                        plot3m(lat,lon,140,'.c','markersize',24);
+                        [latc,longc,] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color','k');
+                    case 2
+                        plot3m(lat, lon, 140, 'pg','markersize',8,'MarkerFaceColor','k');
+                    case 3
+                        plotm(lat, lon,'.c','markersize',24);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color','k');
+                    case 4
+                        plot3m(lat, lon, 140,'dr','markersize',6,'LineWidth',3);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color','k');
+                    case 5
+                        plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color',[.8 .8 .8]);
+                    case 6
+                        plot3m(lat, lon,140,'ok','markersize',6,'linewidth',2.5);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color','k');
+                    case 7
+                        plot3m(lat, lon,140,'og','markersize',6,'linewidth',2.5);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        geoshow(latc,longc,'color','k');
+                    case 8
+                        plot3m(lat, lon,140,'k*','markersize',8);
+                    case 9
+                        plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color',[0 0 0]);
+                    otherwise
+                        plot3m(lat, lon,140,'.','markersize',30,'color',[.8 .8 .8]);
+                        [latc,longc] = scircle1(lat, lon, detection_radius,[],earthradius);
+                        height=ones(numel(latc),1)*200;
+                        plot3m(latc,longc,height,'color',[0 0 0]);
+                end
+                
+                if moorings{o_moorings}.show_name
+                    h2=textm(lat, lon,140, moorings{o_moorings}.name);
+                    set(h2,'Color','k', 'FontSize',6,'FontWeight','bold','HorizontalAlignment','center');
+                end
+                
+            end
+        end
+        clear lat lon detection_radius
+    end
+    
+end 
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m
new file mode 100644
index 0000000000000000000000000000000000000000..5091df554f52e3a24c1a72b31c672c04058356c2
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles.m
@@ -0,0 +1,261 @@
+function [ boolean ] = arrival_of_profiles( p_nemo_data, p_config )
+%ARRIVAL_OF_PROFILES Plots details about iridium data.
+%
+%   Returns:
+%   boolean: False if something went wrong during the plotting process.
+%
+%   Parameters:
+%   p_nemo_data:      Struct, containing all float data (load with nemo.load.data()).
+%   p_config:         Global configuration file. Load using nemo.load.config().
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_data) || ~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Setup directories
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES.storage_directory_fig);
+image_type = p_config.PLOT_ARRIVAL_OF_PROFILES.image_type;
+if ~exist(directory, 'dir')
+    system(['mkdir ' directory]);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Process every float
+
+float_count = length(p_nemo_data);
+
+for o_float = 1:float_count
+    
+    % collect required data
+    
+    % count of profiles that have not been send earlier
+    unsent_profiles_count = nemo.float.collect( ...
+        p_nemo_data{o_float}, ...
+        'PROFILE_TECHNICAL_DATA', ...
+        'xmit_older_profiles_not_send' ...
+    );
+    
+    % average time when the profile has been transmitted
+    iridium_mean_times = nemo.float.collect( ...
+        p_nemo_data{o_float}, ...
+        'IRIDIUM_DATA', ...
+        '' ...
+    );
+    % extract only datevec from it
+    iridium_mean_times = datetime(iridium_mean_times(:, 4:9));
+    
+    % data about every iridium message that has been transmitted for this profile
+    iridium_data = nemo.float.collect( ...
+        p_nemo_data{o_float}, ...
+        'IRIDIUM_POSITIONS', ...
+        '' ...
+    );
+    iridium_message_count = zeros(1, length(iridium_data));
+    % time will be stored in seconds using etime
+    iridium_transmittion_time = zeros(1, length(iridium_data));
+    for o_tmp = 1:length(iridium_data)
+        iridium_message_count(o_tmp) = size(iridium_data{o_tmp}, 1);
+        % get iridium transmittion time if available
+        if iridium_message_count(o_tmp) > 1
+            tmp_min = iridium_data{o_tmp}(1, 4:9);
+            tmp_max = iridium_data{o_tmp}(end, 4:9);
+            iridium_transmittion_time(o_tmp) = etime(tmp_max, tmp_min);
+        else
+            iridium_transmittion_time(o_tmp) = NaN;
+        end
+    end
+    
+    clear o_tmp iridium_data
+    
+    % get numbers of the profiles that have been received
+    transmitted_profiles = nemo.float.collect(p_nemo_data{o_float}, ...
+        'PROFILE_TECHNICAL_DATA', ...
+        'xmit_profile_number' ...
+    );
+
+    % get gps data
+    gps_datetime = nemo.float.collect(p_nemo_data{o_float}, ...
+        'SURFACE_GPS_DATA', ...
+        '' ...
+    );
+
+    best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, ...
+        'SECOND_ORDER_INFORMATION', ...
+        'best_guess_datevec' ...
+        );
+    best_guess_datenum = datenum(best_guess_datevec);
+
+    if isempty(gps_datetime)
+       disp(['No arrival of profiles generated for nemo ' ...
+            num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+            '. No GPS information available!']);
+        continue;
+    end
+    
+    
+    % get plot details
+    width = p_config.PLOT_ARRIVAL_OF_PROFILES.image_width;
+    height = p_config.PLOT_ARRIVAL_OF_PROFILES.image_height;
+    paperunit = p_config.PLOT_ARRIVAL_OF_PROFILES.image_paperunit;
+    
+    % plot collected data
+    
+    figure_id = figure('Visible', 'off'); % create figure
+    % initialize figure
+    set(figure_id,'InvertHardcopy','on');
+    set(figure_id,'PaperUnits', paperunit);
+    papersize = get(figure_id, 'PaperSize');
+    left = (papersize(1)- width)/2;
+    bottom = (papersize(2)- height)/2;
+    myfiguresize = [left, bottom, width, height];
+    set(figure_id,'PaperPosition', myfiguresize);
+    
+    clear height width left bottom papersize myfiguresize
+    
+    %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]);
+    sub = {};
+    
+    sub{1} = subplot(4,1,1);
+    hold on
+    set(gca, 'YLimMode', 'manual', 'YLim', [1, length(transmitted_profiles)+1]);
+%     set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]);
+%     set_x_axis();
+
+    disp(['   Processing arrival information of float number ',num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)])  % OB
+
+    plot(best_guess_datenum, transmitted_profiles,'marker','.','color',[0.5 0.5 0.5],'linestyle','none','linewidth',1,'markersize',20);
+    
+    % OB START: for float 173 iridium_mean_times is of type datetime, which does not
+    % comply with plot command
+    % Old Code:
+    % plot(iridium_mean_times, transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    
+    % New Code
+    if isa(iridium_mean_times,'double')
+        plot(iridium_mean_times, transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    elseif isa(iridium_mean_times,'datetime')
+        plot(datenum(iridium_mean_times), transmitted_profiles,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    end
+    % OB END
+    
+    t = title([ 'Float ' ...
+          num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+          ' @ ' ...
+          datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+        ]);
+    set(t, 'FontSize', 18);
+    clear t
+    ylabel('Profile number');
+    grid on
+    box on
+    hold off
+    
+    % unsent profiles_count
+    sub{2} = subplot(4,1,2);
+    hold on
+%     set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]);
+%     set_x_axis();
+    plot(best_guess_datenum, unsent_profiles_count, 'marker','s','color',[0 0 0],'markerfacecolor',[0 0 0],'linestyle','none','markersize',4);
+    
+    
+    ylabel('Unsent profile count');
+    grid on
+    box on
+    hold off
+    
+    sub{3} = subplot(4,1,3);
+    hold on
+    plot(best_guess_datenum, iridium_transmittion_time./60, '.-','Markersize',15 );
+    ylabel('Transmission time [min]');
+    
+    grid on
+    box on
+    hold off
+    
+    sub{4} = subplot(4,1,4);
+    hold on
+    plot(best_guess_datenum, iridium_message_count, '.-', 'linestyle', 'none','Markersize',15 );
+    
+    ylabel('Message count');
+    grid on
+    box on
+    
+    hold off
+    
+    sub = cellfun(@set_x_axis, sub, 'UniformOutput', false);
+    
+    % store plot
+    
+    image_filename = [p_config.PLOT_ARRIVAL_OF_PROFILES.image_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    figure_filename = [p_config.PLOT_ARRIVAL_OF_PROFILES.figure_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    
+    print(figure_id, [directory image_filename], image_type);
+     
+     if (p_config.PLOT_ARRIVAL_OF_PROFILES.save_figure)
+         set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+         saveas(figure_id, ...
+             [directory_fig figure_filename]);
+     end
+    
+    close(figure_id);
+
+end
+
+boolean = true;
+
+
+    function current_plot = set_x_axis(current_plot)
+        
+        % get min and max datevec
+        min_datenum = min(datenum(best_guess_datevec));
+        max_datenum = max(datenum(best_guess_datevec));
+        if (min_datenum == max_datenum)
+            max_datenum = max_datenum + 1;
+        end
+        diff_datenum = max_datenum - min_datenum;
+        if (diff_datenum > 365)
+            x_tick = [min_datenum:365:max_datenum];
+        elseif (diff_datenum > 30)
+            x_tick = [min_datenum:30:max_datenum];
+        elseif (diff_datenum > 5)
+            x_tick = [min_datenum:5:max_datenum];
+        else
+            x_tick = [min_datenum:1:max_datenum];
+        end
+        if (x_tick(end) < max_datenum)
+            x_tick = [ x_tick max_datenum ];
+        end
+        x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd'));
+        x_tick_labels_top = cellstr(datestr(x_tick, 'yyyy-mm-dd'));
+        
+        if (~isempty(x_tick_labels))
+            x_tick_labels{end} = '';
+        end
+        
+        set(current_plot, 'XLimMode', 'manual', ...
+            'XLim', [min_datenum, max_datenum], ...
+            'XTick', x_tick, ...
+            'XTickLabel', x_tick_labels);
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m
new file mode 100644
index 0000000000000000000000000000000000000000..54f6cf9c7366e10e8a8892964d3f14882c95f795
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/arrival_of_profiles_overview.m
@@ -0,0 +1,232 @@
+function [ boolean ] = arrival_of_profiles_overview( p_nemo_data, p_config )
+%ARRIVAL_OF_PROFILES Plots details about iridium data.
+%
+%   Returns:
+%   boolean: False if something went wrong during the plotting process.
+%
+%   Parameters:
+%   p_nemo_data:      Struct, containing all float data (load with nemo.load.data()).
+%   p_config:         Global configuration file. Load using nemo.load.config().
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_data) || ~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Setup directories
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.storage_directory_fig);
+image_type = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_type;
+if ~exist(directory, 'dir')
+    system(['mkdir ' directory]);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+
+%% Collect required data
+
+first_deployment_datenum = NaN; % contains the earliest deployment date of all floats
+latest_profile_datenum = NaN; % contains the latest profile that has been transmitted
+
+best_guess_datenum = {};
+transmitted_profiles = {};
+iridium_mean_times = {};
+float_serials = [];
+
+float_count = length(p_nemo_data);
+
+for o_float = 1:float_count
+
+    % get serial number
+    float_serials = [float_serials; p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number];
+    
+    % get the deployment dates
+    current_deployment_datenum = datenum( ...
+        [ ...
+          p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_date ...
+          p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_time ...
+          0 ...
+        ] ...
+    );
+    if isnan(first_deployment_datenum) || (current_deployment_datenum < first_deployment_datenum)
+        first_deployment_datenum = current_deployment_datenum;
+    end
+    current_latest_profile_datenum = datenum(p_nemo_data{o_float}{end}.SECOND_ORDER_INFORMATION.best_guess_datevec);
+    % get latest profile date
+    if ~all(isnan(current_latest_profile_datenum)) && (isnan(latest_profile_datenum) || (current_latest_profile_datenum > latest_profile_datenum))
+        latest_profile_datenum = current_latest_profile_datenum;
+    end
+    
+    % check if gps data available
+    gps_datetime = nemo.float.collect(p_nemo_data{o_float}, ...
+        'SURFACE_GPS_DATA', ...
+        '' ...
+    );
+    if isempty(gps_datetime)
+        continue;
+    end
+    
+    
+    % collect required data
+    
+    best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, ...
+        'SECOND_ORDER_INFORMATION', ...
+        'best_guess_datevec' ...
+        );
+    best_guess_datenum{o_float} = datenum(best_guess_datevec);
+    
+    % get numbers of the profiles that have been received
+    transmitted_profiles{o_float} = nemo.float.collect(p_nemo_data{o_float}, ...
+        'PROFILE_TECHNICAL_DATA', ...
+        'xmit_profile_number' ...
+    );
+
+    % average time when the profile has been transmitted
+    iridium_mean_times{o_float} = nemo.float.collect( ...
+        p_nemo_data{o_float}, ...
+        'IRIDIUM_DATA', ...
+        '' ...
+    );
+    % extract only datevec from it
+    iridium_mean_times{o_float} = datetime(iridium_mean_times{o_float}(:, 4:9));
+    
+    
+end
+
+% get min and max datevec
+if (first_deployment_datenum == latest_profile_datenum)
+    latest_profile_datenum = latest_profile_datenum + 1;
+end
+diff_datenum = latest_profile_datenum - first_deployment_datenum;
+if (diff_datenum > 365)
+    x_tick = [first_deployment_datenum:365:latest_profile_datenum];
+elseif (diff_datenum > 30)
+    x_tick = [first_deployment_datenum:30:latest_profile_datenum];
+elseif (diff_datenum > 5)
+    x_tick = [first_deployment_datenum:5:latest_profile_datenum];
+else
+    x_tick = [first_deployment_datenum:1:latest_profile_datenum];
+end
+x_tick = [ x_tick(1:end-1) latest_profile_datenum ];
+    
+% get plot details
+width = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_width;
+height = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_height;
+paperunit = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_paperunit;
+
+% plot collected data
+
+figure_id = figure('Visible', 'off'); % create figure
+% initialize figure
+set(figure_id,'InvertHardcopy','on');
+set(figure_id,'PaperUnits', paperunit);
+papersize = get(figure_id, 'PaperSize');
+left = (papersize(1)- width)/2;
+bottom = (papersize(2)- height)/2;
+myfiguresize = [left, bottom, width, height];
+set(figure_id,'PaperPosition', myfiguresize);
+clear height width left bottom papersize myfiguresize
+
+%set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]);
+sub = {};
+
+sub{1} = subplot(2,1,1);
+hold on
+%     set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]);
+%     set_x_axis();
+for o_float = 1:float_count
+    y = repmat(float_serials(o_float), length(transmitted_profiles{o_float}), 1);
+    plot(transmitted_profiles{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    clear y
+end
+
+t = title([ 'Arrival of profiles overview @ ' ...
+      datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+    ]);
+set(t, 'FontSize', 18);
+clear t
+ylabel('Float serial number');
+xlabel('Profile number');
+grid on
+box on
+hold off
+
+
+
+sub{2} = subplot(2,1,2);
+hold on
+%     set(gca, 'XLimMode', 'manual', 'XLim', [min_datenum, max_datenum]);
+%     set_x_axis();
+for o_float = 1:float_count
+    
+    disp(['   Processing arrival overview information of float number ',num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)])  % OB
+    
+    y = repmat(float_serials(o_float), length(best_guess_datenum{o_float}), 1);
+    plot(best_guess_datenum{o_float}, y,'marker','.','color',[0.5 0.5 0.5],'linestyle','none','linewidth',1,'markersize',20);
+    y = repmat(float_serials(o_float), length(transmitted_profiles{o_float}), 1);
+            
+    % OB START: for float 173 iridium_mean_times is of type datetime, which does not
+    % comply with plot command
+    % Old Code:
+    % plot(iridium_mean_times{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    % New Code
+    if isa(iridium_mean_times{o_float},'double')
+        plot(iridium_mean_times{o_float}, y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    elseif isa(iridium_mean_times{o_float},'datetime')
+        plot(datenum(iridium_mean_times{o_float}), y,'marker','.','color',[0 0 0],'linestyle','none','linewidth',1,'markersize',20);
+    end
+    % OB END
+    
+    clear y
+end
+
+ylabel('Float serial number');
+xlabel('Iridium timestamp');
+grid on
+box on
+hold off
+
+% set_x_axis(sub{2});
+
+
+% store plot
+
+image_filename = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.image_filename;
+figure_filename = p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.figure_filename;
+
+print(figure_id, [directory image_filename], image_type);
+
+ if (p_config.PLOT_ARRIVAL_OF_PROFILES_OVERVIEW.save_figure)
+     saveas(figure_id, ...
+         [directory_fig figure_filename]);
+ end
+
+close(figure_id);
+
+boolean = true;
+
+
+    function current_plot = set_x_axis(current_plot)
+        set(current_plot, 'XLimMode', 'manual', ...
+                          'XLim', [first_deployment_datenum, latest_profile_datenum], ...
+                          'XTick', x_tick);
+        datetick(current_plot, 'x', 'yyyy-mm-dd', 'keepticks');
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m b/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m
new file mode 100644
index 0000000000000000000000000000000000000000..21cdcbca5abea9f1083fcfede8ab753de80f58ca
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/ascent.m
@@ -0,0 +1,207 @@
+function [ boolean ] = ascent( p_nemo_data, p_config )
+%ASCENT Generates plots showing the ascent data of the float.
+%   Generates a plot for every float in p_nemo_data containing the depth on
+%   recorded time of day, profile recording time on profile number as well
+%   as 0 to 50m depth on profile number.
+%
+%   Parameters:
+%   p_nemo_data:            The float data.
+%   p_config:               The nemo configuration.
+%
+%   Returns:
+%   boolean:                True if successful.
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check %TODO
+
+%% Get config parameter
+% get plot details
+width = p_config.PLOT_ASCENT.image_width;
+height = p_config.PLOT_ASCENT.image_height;
+paperunit = p_config.PLOT_ASCENT.image_paperunit;
+image_type = p_config.PLOT_ASCENT.image_type;
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ASCENT.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_ASCENT.storage_directory_fig);
+ylabel_fontsize = p_config.PLOT_ASCENT.ylabel_fontsize;
+xlabel_fontsize = p_config.PLOT_ASCENT.ylabel_fontsize;
+axis_fontsize = p_config.PLOT_ASCENT.axis_fontsize;
+title_fontsize = p_config.PLOT_ASCENT.title_fontsize;
+
+if (~isdir(directory))
+    mkdir(directory);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Collect required data
+
+switch (class(p_nemo_data{1}))
+    case 'struct' % only one float is to be processed
+        boolean = plot_float(p_nemo_data);
+    case 'cell' % more than one float has to be processed
+        boolean = all(cellfun(@plot_float, p_nemo_data));
+    otherwise
+        disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']);
+end
+
+    function boolean = plot_float(p_float)
+        boolean = false;
+        profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', '');
+        profile_best_guess_datevec = nemo.float.collect(p_float, ...
+                                               'SECOND_ORDER_INFORMATION', ...
+                                               'best_guess_datevec');
+%         profiles_best_guess_datenum = datenum([1970, 1, 1, profiles_best_guess_datevec(:, 4:6)]);
+        if (isempty(profile_data))
+            boolean = true;
+            return
+        end
+        
+        % initialize figure
+        figure_id = figure('Visible', 'Off');
+        set(figure_id,'InvertHardcopy','on');
+        set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]);
+        papersize = get(figure_id, 'PaperSize');
+        left = (papersize(1) - width)/2;
+        bottom = (papersize(2) - height)/2;
+        myfiguresize = [left, bottom, width, height];
+        set(figure_id,'PaperPosition', myfiguresize);
+        
+        % Depth on time plot
+        sub(1) = subplot(2, 2, 1);
+        hold on
+        set(gca, 'YLimMode', 'manual', 'YLim', [0 2000], ...
+            'YTick', [0:200:2000], 'YDir', 'reverse');
+        xlimits = [datenum(0, 0, 0, 0, 0, 0) datenum(0, 0, 0, 24, 0, 0)];
+        set(gca, 'XLim', xlimits, 'XTick', linspace(xlimits(1), xlimits(2), 7));
+        for i = 1:length(profile_data)
+            plot_x = mod(datenum([ ...
+                                  repmat(1970, length(profile_data{i}), 1) ...
+                                  , ones(length(profile_data{i}), 1) ...
+                                  , ones(length(profile_data{i}), 1) ...
+                                  , profile_data{i}(:, 2:4)]), 1);
+            plot_y = profile_data{i}(:, 7);
+            plot_c = [0 0 1] * i/length(profile_data);
+            scatter(plot_x, plot_y, 30, plot_c, 'filled');
+        end
+        datetick('x','HH:MM', 'keepticks', 'keeplimits');
+        a = get(gca, 'YTickLabel');
+        set(gca, 'YTickLabel', a, 'fontsize', axis_fontsize)
+        a = get(gca, 'XTickLabel');
+        set(gca, 'XTickLabel', a, 'fontsize', axis_fontsize)
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        xlabel('Time of day', 'FontSize', xlabel_fontsize);
+        box on
+        grid on
+        hold off
+        
+        % Profile data time on profile number
+        sub(2) = subplot(2, 2, 3);
+        hold on
+        ylimits = [datenum(0, 0, 0, 0, 0, 0) datenum(0, 0, 0, 24, 0, 0)];
+        set(gca, 'YLimMode', 'manual', 'YLim', ylimits, 'YTick', linspace(ylimits(1), ylimits(2), 7));
+        % get required information about x-axis limits and ticks
+        profile_best_guess_datenum = datenum(profile_best_guess_datevec);
+%         start_datenum = min(profile_best_guess_datenum);
+%         end_datenum = max(profile_best_guess_datenum);
+%         x_diff = max(profile_best_guess_datenum) ...
+%                      - min(profile_best_guess_datenum);
+%         if (start_datenum ~= end_datenum)
+%             set(gca, ...
+%                 'XLimMode', 'manual', ...
+%                 'XLim', [start_datenum, end_datenum]);
+%         end
+%         if (x_diff < 30)
+%             set(gca, 'XTick', [start_datenum:5:end_datenum]);
+%         elseif (x_diff < 365)
+%             set(gca, 'XTick', [start_datenum:30:end_datenum]);
+%         else
+%             set(gca, 'XTick', [start_datenum:365:end_datenum]);
+%         end
+        for i = 1:length(profile_data)
+            plot_x = ones(1, length(profile_data{i}(:, 1))) * i;
+            plot_y = datenum([zeros(length(profile_data{i}(:, 1)), 1), ...
+                              zeros(length(profile_data{i}(:, 1)), 1), ...
+                              zeros(length(profile_data{i}(:, 1)), 1), ...
+                              profile_data{i}(:,2:4)]);
+            scatter(plot_x, plot_y, 30, [0 0 0], 'filled');
+        end
+        datetick('y','HH:MM', 'keepticks', 'keeplimits');
+%         datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits');
+        set(gca, 'fontsize', axis_fontsize)
+        ylabel('Profile recording time [HH:MM]', 'FontSize', ylabel_fontsize);
+        xlabel('Profile number', 'FontSize', xlabel_fontsize);
+        box on
+        grid on
+        hold off
+        
+        % Zoom on upper 50m water column
+        sub(3) = subplot(1, 2, 2);
+        hold on
+        set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 50], ...
+            'XLimMode', 'auto');
+        set(gca, 'fontsize', axis_fontsize)
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        xlabel('Profile number', 'FontSize', xlabel_fontsize);
+        % get required information about x-axis limits and ticks
+        for i = 1:length(profile_data)
+            temperature = profile_data{i}(:, 5);
+            pressure = profile_data{i}(:, 7);
+            % erase all invalid values
+            valid_points = (pressure >= 0) & (pressure <= 50);
+            temperature = temperature(valid_points);
+            pressure = pressure(valid_points);
+            x_axis = ones(size(pressure, 1), 1) * i;
+            scatter(x_axis, pressure, 45, temperature, 'filled');
+        end
+        c = colorbar;
+        cmocean('thermal');
+        %colormap(gca, 'jet');
+        %caxis([-5, 15]);
+        c.Label.String = 'Temperature [°C]';
+        clear c
+        
+        
+        % create main title
+        if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0)
+            deployment_string = ' (Arctic)';
+        else
+            deployment_string = ' (Antarctica)';
+        end
+        t = title([ 'Float ' ...
+               num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+               ' @ ' ...
+               datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+               deployment_string ...
+        ]);
+        set(t, 'FontSize', title_fontsize);
+        clear t
+        
+        box on
+        grid on
+        hold off
+         
+        set(gcf, 'Renderer', 'painters');
+        print(figure_id, ...
+         [directory p_config.PLOT_ASCENT.image_filename ...
+           num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ...
+         , image_type);
+     
+        if (p_config.PLOT_ASCENT.save_figure)
+            set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+            saveas(figure_id, ...
+                [directory_fig ...
+                 p_config.PLOT_ASCENT.figure_filename ...
+                 num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]);
+        end
+        
+        close(figure_id);
+        clear figure_id f
+        boolean = true;
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m b/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m
new file mode 100644
index 0000000000000000000000000000000000000000..bef2ee27757e696661a70c09d779723890c3b42c
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/categorized_timings.m
@@ -0,0 +1,327 @@
+function [ boolean ] = categorized_timings( p_nemo_data, p_config )
+%TIMING_INFORMATION Plots clock details.
+%
+%   Returns:
+%   boolean: False if something went wrong during the plotting process.
+%
+%   Parameters:
+%   p_nemo_data:      Struct, containing all float data (load with nemo.load.data()).
+%   p_config:         Global configuration file. Load using nemo.load.config().
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_data) || ~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Setup directories
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_CATEGORIZED_TIMINGS.storage_directory_fig);
+image_type = p_config.PLOT_CATEGORIZED_TIMINGS.image_type;
+if ~exist(directory, 'dir')
+    system(['mkdir ' directory]);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Process every float
+
+float_count = length(p_nemo_data);
+
+for o_float = 1:float_count
+    
+    current_float = p_nemo_data{o_float};
+    
+    start_date = current_float{end}.OVERALL_MISSION_INFORMATION.start_date;
+    start_time = current_float{end}.OVERALL_MISSION_INFORMATION.start_time;
+    xmit_ascent_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_ascent_start_time');
+    xmit_ascent_end_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_ascent_end_time');
+    xmit_descent_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_descent_start_time');
+    xmit_parking_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_parking_start_time');
+    xmit_upcast_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_upcast_start_time');
+    xmit_surface_start_time = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_surface_start_time');
+    xmit_profile_number = nemo.float.collect(current_float, 'PROFILE_TECHNICAL_DATA', 'xmit_profile_number');
+    
+    combined_xmit_times = [ ...
+        xmit_descent_start_time, ...
+        xmit_parking_start_time, ...
+        xmit_upcast_start_time, ...
+        xmit_ascent_start_time, ...
+        xmit_ascent_end_time, ...
+        xmit_surface_start_time ...
+        ];
+    
+    combined_diff_times = [ ...
+%         NaN(length(xmit_descent_start_time), 1), ...
+        (xmit_descent_start_time - xmit_parking_start_time), ...
+        (xmit_parking_start_time - xmit_upcast_start_time), ...
+        (xmit_upcast_start_time - xmit_ascent_start_time), ...
+        (xmit_ascent_start_time - xmit_ascent_end_time), ...
+        (xmit_ascent_end_time - xmit_surface_start_time), ...
+        [NaN; (xmit_surface_start_time(1:end -1) - xmit_descent_start_time(2:end))]
+        ];
+
+    [p_row, ~] = size(combined_xmit_times);
+    tmp_xmit_times = [];
+    tmp_diff_times = [];
+    for o_row = 1:p_row
+        tmp_xmit_times = [ tmp_xmit_times; combined_xmit_times(o_row, :)'];
+        tmp_diff_times = [ tmp_diff_times; combined_diff_times(o_row, :)'];
+    end
+    % categorize them
+    combined_xmit_times = [repmat([3:6, 2, 1]', length(tmp_xmit_times)/6, 1), tmp_xmit_times];
+    combined_diff_times = [repmat([3:6, 2, 1]', length(tmp_diff_times)/6, 1), tmp_diff_times];
+    clear tmp_xmit_times tmp_diff_times
+    
+    
+    % get plot details
+    width = p_config.PLOT_CATEGORIZED_TIMINGS.image_width;
+    height = p_config.PLOT_CATEGORIZED_TIMINGS.image_height;
+    paperunit = p_config.PLOT_CATEGORIZED_TIMINGS.image_paperunit;
+    
+    % plot collected data
+    
+    figure_id = figure('Visible', 'off'); % create figure
+    % initialize figure
+    set(figure_id,'InvertHardcopy','on');
+    set(figure_id,'PaperUnits', paperunit);
+    papersize = get(figure_id, 'PaperSize');
+    left = (papersize(1)- width)/2;
+    bottom = (papersize(2)- height)/2;
+    myfiguresize = [left, bottom, width, height];
+    set(figure_id,'PaperPosition', myfiguresize);
+    
+    clear height width left bottom papersize myfiguresize
+    
+    %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]);
+    sub = {};
+    
+    sub{1} = subplot(2,1,1);
+    hold on
+
+
+    plot(combined_xmit_times(:, 2), combined_xmit_times(:, 1), ...
+         'color', [0 0 0], ...
+         'linestyle', '-', ...
+         'linewidth', 1 ...
+     );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 3, 2), combined_xmit_times(combined_xmit_times(:, 1) == 3, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 0 0], ...
+         'MarkerEdgeColor', [1 0 0] ...
+         );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 4, 2), combined_xmit_times(combined_xmit_times(:, 1) == 4, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 1 0], ...
+         'MarkerEdgeColor', [0 1 0] ...
+         );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 5, 2), combined_xmit_times(combined_xmit_times(:, 1) == 5, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 0 1], ...
+         'MarkerEdgeColor', [0 0 1] ...
+         );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 6, 2), combined_xmit_times(combined_xmit_times(:, 1) == 6, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 1 0], ...
+         'MarkerEdgeColor', [1 1 0] ...
+         );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 2, 2), combined_xmit_times(combined_xmit_times(:, 1) == 2, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 1 1], ...
+         'MarkerEdgeColor', [0 1 1] ...
+         );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 1, 2), combined_xmit_times(combined_xmit_times(:, 1) == 1, 1), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 0 1], ...
+         'MarkerEdgeColor', [1 0 1] ...
+         );
+
+    ylabel('Category');
+    xlabel('Seconds since start');
+    axis ij;
+%     legend('SurfaceStart-GPS', 'SurfaceStart-IRIDIUM', 'AscentStart-RTC2000');
+    leg1 = legend('TimesConnected', 'DescentStart', 'ParkingStart', 'UpcastStart', 'AscentStart', 'AscentEnd', 'SurfaceStart');
+    set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical');
+    clear leg1
+    t = title(...
+        [ 'Float ' ...
+          num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+          ' @ ' ...
+          datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+        ]);
+    set(t, 'FontSize', 18);
+    clear t
+    grid on
+    box on
+    hold off
+    
+    sub{2} = subplot(2,1,2);
+    hold on
+
+
+    plot(combined_xmit_times(:, 2), combined_diff_times(:, 2), ...
+         'color', [0 0 0], ...
+         'linestyle', '-', ...
+         'linewidth', 1 ...
+     );
+     scatter(combined_xmit_times(combined_xmit_times(:, 1) == 3, 2), combined_diff_times(combined_diff_times(:, 1) == 3, 2), ...
+         'marker', 'o', ...
+         'MarkerEdgeColor', [1 0 0] ...
+     );
+    scatter(combined_xmit_times(combined_xmit_times(:, 1) == 4, 2), combined_diff_times(combined_diff_times(:, 1) == 4, 2), ...
+         'marker', 'o', ...
+         'MarkerEdgeColor', [0 1 0] ...
+     );
+    scatter(combined_xmit_times(combined_xmit_times(:, 1) == 5, 2), combined_diff_times(combined_diff_times(:, 1) == 5, 2), ...
+         'marker', 'o', ...
+         'MarkerEdgeColor', [0 0 1] ...
+     );
+    scatter(combined_xmit_times(combined_xmit_times(:, 1) == 6, 2), combined_diff_times(combined_diff_times(:, 1) == 6, 2), ...
+         'marker', 'o', ...
+         'MarkerEdgeColor', [1 1 0] ...
+     );
+    scatter(combined_xmit_times(combined_xmit_times(:, 1) == 2, 2), combined_diff_times(combined_diff_times(:, 1) == 2, 2), ...    
+         'marker', 'o', ...
+         'MarkerEdgeColor', [0 1 1] ...
+     );
+    scatter(combined_xmit_times(combined_xmit_times(:, 1) == 1, 2), combined_diff_times(combined_diff_times(:, 1) == 1, 2), ...
+         'marker', 'o', ...
+         'MarkerEdgeColor', [1 0 1] ...
+     );
+ 
+ 
+    % plot all times > 0 and hide them from legend
+    selector = combined_diff_times(:, 1) == 3 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 0 0], ...
+         'MarkerEdgeColor', [1 0 0] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    selector = combined_diff_times(:, 1) == 4 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 1 0], ...
+         'MarkerEdgeColor', [0 1 0] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    selector = combined_diff_times(:, 1) == 5 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 0 1], ...
+         'MarkerEdgeColor', [0 0 1] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    selector = combined_diff_times(:, 1) == 6 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 1 0], ...
+         'MarkerEdgeColor', [1 1 0] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    selector = combined_diff_times(:, 1) == 2 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...    
+         'marker', 'o', ...
+         'MarkerFaceColor', [0 1 1], ...
+         'MarkerEdgeColor', [0 1 1] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    selector = combined_diff_times(:, 1) == 1 & combined_diff_times(:, 2) > 0;
+    scat1 = scatter(combined_xmit_times(selector, 2), combined_diff_times(selector, 2), ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 0 1], ...
+         'MarkerEdgeColor', [1 0 1] ...
+     );
+    hasbehavior(scat1, 'legend', false);
+    clear scat1
+ 
+ 
+    ylabel('Time difference [sec]');
+    xlabel('Seconds since start');
+%     axis ij;
+%     legend('SurfaceStart-GPS', 'SurfaceStart-IRIDIUM', 'AscentStart-RTC2000');
+    leg1 = legend('DiffsConnected', 'DescentStart-ParkingStart', 'ParkingStart-UpcastStart', 'UpcastStart-AscentStart', 'AscentStart-AscentEnd', 'AscentEnd-SurfaceStart', 'SurfaceStart-DescentStart');
+    set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical');
+    clear leg1
+    grid on
+    box on
+    hold off
+    
+    sub1_pos = get(sub{1}, 'Position');
+    sub2_pos = get(sub{2}, 'Position');
+%     leg1_pos = get(leg1, 'Position');
+    set(sub{1}, 'Position', [sub1_pos(1:2) 0.6652 sub1_pos(4)]);
+    set(sub{2}, 'Position', [sub2_pos(1:2) 0.6652 sub2_pos(4)]);
+    
+    % store plot
+    
+    image_filename = [p_config.PLOT_CATEGORIZED_TIMINGS.image_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    figure_filename = [p_config.PLOT_CATEGORIZED_TIMINGS.figure_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    
+    if (p_config.PLOT_CATEGORIZED_TIMINGS.save_figure)
+        set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+        saveas(figure_id, ...
+               [directory_fig figure_filename]);
+     end
+    
+    
+    print(figure_id, [directory image_filename], image_type);
+    
+    close(figure_id);
+
+end
+
+boolean = true;
+
+
+    function current_plot = set_x_axis(current_plot)
+        
+        best_guess_datevec = nemo.float.collect(current_float, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec');
+        % get min and max datevec
+        min_datenum = min(datenum(best_guess_datevec));
+        max_datenum = max(datenum(best_guess_datevec));
+        if (min_datenum == max_datenum)
+            max_datenum = max_datenum + 1;
+        end
+        diff_datenum = max_datenum - min_datenum;
+        if (diff_datenum > 365)
+            x_tick = [min_datenum:365:max_datenum];
+        elseif (diff_datenum > 30)
+            x_tick = [min_datenum:30:max_datenum];
+        elseif (diff_datenum > 5)
+            x_tick = [min_datenum:5:max_datenum];
+        else
+            x_tick = [min_datenum:1:max_datenum];
+        end
+        if (x_tick(end) < max_datenum)
+            x_tick = [ x_tick max_datenum ];
+        end
+        x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd'));
+        
+        if (~isempty(x_tick_labels))
+            x_tick_labels{end} = '';
+        end
+        
+        set(current_plot, 'XLimMode', 'manual', ...
+            'XLim', [min_datenum, max_datenum], ...
+            'XTick', x_tick, ...
+            'XTickLabel', x_tick_labels);
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m b/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m
new file mode 100644
index 0000000000000000000000000000000000000000..dc664968398e21f6e26cbfa32c9c2160a8eb7e66
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/float_to_template_south.m
@@ -0,0 +1,137 @@
+function [ modification_history ] = float_to_template_south( p_nemo_float, p_template, p_config, p_single_float )
+%FLOAT_TO_TEMPLATE_SOUTH Plots the float positions to the given template. 
+%
+%   Parameters:
+%   p_nemo_float:           The float data, can be more than one.
+%   p_template:             The template generated by
+%                           nemo.plots.templates.create_south function
+%   p_config:               The nemo configuration.
+%   p_single_float:         If true, just one float is being plotted to the
+%                           template at a time. If false, all floats will
+%                           be plotted into the plot.
+%   
+%   Returns:
+%   modification_history:   The modifications made by this function to the
+%                           template.
+
+% make template the current figure
+set(0, 'currentfigure', p_template);
+
+% get style of plot
+plot_style = p_config.PLOT_STYLE_SOUTH;
+
+if p_single_float
+    show_profile_numbers = p_config.PLOT_SINGLE_FLOAT_SOUTH.show_profile_numbers;
+else
+    show_profile_numbers = p_config.PLOT_OVERVIEW_SOUTH.show_profile_numbers;
+end
+
+modification_history = [];
+
+profile_count = length(p_nemo_float);
+bgp_lat = nan(profile_count, 1);
+bgp_lon = nan(profile_count, 1);
+
+% store the first position manually, because then its possible to save
+% the source of the previous index
+% best guess positions (abbr. bgp)
+bgp_lat(1) = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat;
+bgp_lon(1) = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lon;
+bgp_src{1} = p_nemo_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_src;
+profile_numbers(1, 1) = p_nemo_float{1}.PROFILE_TECHNICAL_DATA.xmit_profile_number;
+measured_ranges = []; % ranges of measured positions will be stored in here
+
+if (strcmp(bgp_src{1}, 'gps') || strcmp(bgp_src{1}, 'iridium'))
+    % it is a measured position, so save the index!
+    measured_start_index = 1;
+else
+    measured_start_index = 0; % because zero is not a valid index
+end
+
+for ii=2:profile_count
+    
+    % best guess positions (abbr. bgp)
+    bgp_lat(ii) = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_lat;
+    bgp_lon(ii) = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_lon;
+    bgp_src{ii} = p_nemo_float{ii}.SECOND_ORDER_INFORMATION.best_guess_position_src;
+    profile_numbers(ii, 1) = p_nemo_float{ii}.PROFILE_TECHNICAL_DATA.xmit_profile_number;
+    
+    % get all pairs of measured positions
+    if (measured_start_index == 0) && (strcmp(bgp_src{ii}, 'gps') || strcmp(bgp_src{ii}, 'iridium'))
+        % no starting index is set, but a measured position has been
+        % found
+        measured_start_index = ii;
+    elseif (measured_start_index > 0) && ((~strcmp(bgp_src{ii}, 'gps') && ~strcmp(bgp_src{ii}, 'iridium')))
+        % starting index valid, but a not measured position has been
+        % found, so store endpoint
+        measured_ranges = [measured_ranges; [measured_start_index ii-1]];
+        measured_start_index = 0; % set invalid start index
+    elseif ((measured_start_index > 0) && (ii == profile_count))
+        % the last position is also valid
+        measured_ranges = [measured_ranges; [measured_start_index ii]];
+        measured_start_index = 0; % set invalid start index
+    end
+    
+end
+
+% draw all bgp positions first
+modification_history(end+1) = plotm(bgp_lat,bgp_lon,'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color_interpolated, 'linestyle', '-');   %  GPS positions
+
+% get deployment position
+dep_lat = p_nemo_float{1}.DEPLOYMENT_INFO.deployment_position(1);
+dep_lon = p_nemo_float{1}.DEPLOYMENT_INFO.deployment_position(2);
+
+if (~isempty(measured_ranges)) && (measured_ranges(1, 1) == 1) % first position is a measured position
+    % plot a black line from deployment position to first profile
+    % position
+    modification_history(end+1) = plotm([dep_lat; bgp_lat(1)], [dep_lon; bgp_lon(1)],'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color, 'linestyle', '-');   %  GPS positions
+else
+    % plot a interpolated line
+    modification_history(end+1) = plotm([dep_lat; bgp_lat(1)], [dep_lon; bgp_lon(1)],'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.gps_color_interpolated, 'linestyle', '-');
+end
+
+% draw all measured (iridium or gps) positions above it
+measured_length = size(measured_ranges, 1);
+for o_measured_index = 1:measured_length
+    modification_history(end+1) = ...
+    plotm(bgp_lat(measured_ranges(o_measured_index, 1):measured_ranges(o_measured_index, 2)), ...
+        bgp_lon(measured_ranges(o_measured_index, 1):measured_ranges(o_measured_index, 2)), ...
+        'marker',plot_style.gps_marker,...
+        'markersize',plot_style.gps_markersize, ...
+        'color', plot_style.gps_color, 'linestyle', '-');   %  GPS positions
+end
+
+% plot deployment position
+modification_history(end+1) = plotm(dep_lat,dep_lon,150,'marker',plot_style.deploy_marker,'markersize',plot_style.deploy_markersize,'color',plot_style.deploy_color,'linestyle','none');   % plot deplyoment position as crossed square
+
+% plot last transmittion
+modification_history(end+1) = plotm(bgp_lat(end),bgp_lon(end),150,'marker',plot_style.gps_marker,'markersize',plot_style.gps_markersize,'color',plot_style.last_transmittion_color,'linestyle','none');    % plot final gps position
+% plot serial number of float to the last transmitted profile
+text_serial_number = textm(bgp_lat(end)+0.2,bgp_lon(end)+0.2,num2str(p_nemo_float{end}.FLOAT_IDENTIFICATION.float_serial_number));
+set(text_serial_number,'interpreter','none', 'FontSize', 6,'FontWeight','bold');
+modification_history(end+1) = text_serial_number;
+clear measured_ranges measured_length measured_start_index profile_count text_serial_number
+
+% change figure title
+if p_single_float
+    set(get(gca, 'title'), 'String', ['Float ' num2str(p_nemo_float{end}.FLOAT_IDENTIFICATION.float_serial_number) ', ' datestr(now) ]);
+else
+    set(get(gca, 'title'), 'String', datestr(now));
+end
+
+
+if show_profile_numbers == 1
+    % plot profile numbers
+    text_profile_numbers = textm(bgp_lat(10:10:end)+0.2,bgp_lon(10:10:end)+0.2,num2str(profile_numbers(10:10:end)));
+    set(text_profile_numbers,'interpreter','none', 'FontSize', 6,'FontWeight','bold');
+    if length(text_profile_numbers) > 1
+        modification_history = [modification_history, text_profile_numbers'];
+    elseif length(text_profile_numbers) == 1
+        modification_history(end+1) = text_profile_numbers;
+    end
+end
+
+clear bgp_lat bgp_lon dep_lat dep_lon bgp_src profile_numbers text_profile_numbers
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m b/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m
new file mode 100644
index 0000000000000000000000000000000000000000..d99118535171b43f0fa1d2d12b0d36fda2cbf4a3
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/floats.m
@@ -0,0 +1,128 @@
+function [ boolean ] = floats(p_nemo_data, p_config, p_hemisphere, p_single_float)
+%FLOATS Creates a template figure and plots the given floats on it.
+%
+%   Returns:
+%   boolean:    True if everything went successful.
+%
+%   Parameters:
+%   p_nemo_data:        Struct containing float data, load with nemo.load.data().
+%   p_config:           Struct containing the global configuration file. Load
+%                       using nemo.load.config().
+%   p_hemisphere:       String, either 'south' or 'north'. If it is given an
+%                       empty string, both hemispheres are being plotted.
+%                       WARNING: North is not implemented yet.
+%   p_single_float:     If true, every float will be plotted in a separate plot.
+
+
+%% Initialize return variable
+boolean = false;
+
+%% Update sea ice coverage
+if p_config.GENERAL.update_sea_ice_coverage
+    try
+        websave(p_config.ADDITIONAL_CONTENT.antarctic_sea_ice_coverage_file, ...
+            p_config.REMOTE_CONTENT.antarctic_sea_ice_coverage);
+    catch ex
+        warning([mfilename ': Updating sea ice coverage failed with error: ' ...
+            getReport(ex)]);
+    end
+end
+
+% run through every float and generate an overview plot
+float_count = length(p_nemo_data);
+
+switch p_hemisphere
+    case 'south'
+        % prepare directory and parameters
+        if p_single_float
+            directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_image);
+            directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_SINGLE_FLOAT_SOUTH.storage_directory_fig);
+            image_type = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_type;
+            width = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_width;
+            height = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_height;
+            paperunit = p_config.PLOT_SINGLE_FLOAT_SOUTH.image_paperunit;
+        else
+            directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_OVERVIEW_SOUTH.storage_directory_image);
+            directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_OVERVIEW_SOUTH.storage_directory_fig);
+            image_type = p_config.PLOT_OVERVIEW_SOUTH.image_type;
+            image_filename = p_config.PLOT_OVERVIEW_SOUTH.image_filename;
+            figure_filename = p_config.PLOT_OVERVIEW_SOUTH.figure_filename;
+            width = p_config.PLOT_OVERVIEW_SOUTH.image_width;
+            height = p_config.PLOT_OVERVIEW_SOUTH.image_height;
+            paperunit = p_config.PLOT_OVERVIEW_SOUTH.image_paperunit;
+        end
+        if ~exist(directory,'dir')
+            mkdir(directory);
+        end
+        if ~exist(directory_fig,'dir')
+            mkdir(directory_fig);
+        end
+        
+        template_id_south = nemo.plot.templates.create_south(p_config);
+        for o_float = 1:float_count
+            if p_nemo_data{o_float}{1}.DEPLOYMENT_INFO.deployment_position(1) > 0
+                continue
+            end
+            float_serial_number = ...
+                p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number;
+            modification_history = ...
+                nemo.plot.float_to_template_south(p_nemo_data{o_float}, ...
+                template_id_south, p_config, p_single_float);
+            
+            if p_single_float
+                %saveas(template_id_south,[directory num2str(float_serial_number) '.' image_type], image_type);
+                set(template_id_south,'InvertHardcopy','on');
+                set(template_id_south,'PaperUnits', paperunit);
+                papersize = get(template_id_south, 'PaperSize');
+                left = (papersize(1)- width)/2;
+                bottom = (papersize(2)- height)/2;
+                myfiguresize = [left, bottom, width, height];
+                set(template_id_south,'PaperPosition', myfiguresize);
+                
+                print(template_id_south, ...
+                    [directory num2str(float_serial_number)], image_type);
+                
+                if (p_config.PLOT_SINGLE_FLOAT_SOUTH.save_figure)
+                    set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+                    saveas(template_id_south,[directory_fig num2str(float_serial_number)]);
+                end
+                delete(modification_history);
+            end
+            clear modification_history
+        end
+        
+        if ~p_single_float
+            
+            set(template_id_south,'InvertHardcopy','on');
+            set(template_id_south,'PaperUnits', paperunit);
+            papersize = get(template_id_south, 'PaperSize');
+            left = (papersize(1)- width)/2;
+            bottom = (papersize(2)- height)/2;
+            myfiguresize = [left, bottom, width, height];
+            set(template_id_south,'PaperPosition', myfiguresize);
+            
+            print(template_id_south, [directory image_filename], image_type);
+            
+            if (p_config.PLOT_OVERVIEW_SOUTH.save_figure)
+                set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+                saveas(template_id_south, ...
+                    [directory_fig ...
+                    figure_filename]);
+            end
+        end
+        
+        close(template_id_south);
+    case 'north'
+            warning('Northern overview plot is not implemented yet...');
+    otherwise
+        % if anything else is given, just plot both hemispheres
+        nemo.plot.floats(p_nemo_data, p_config, 'south', p_single_float);
+        nemo.plot.floats(p_nemo_data, p_config, 'north', p_single_float);
+end
+
+boolean = true;
+
+return
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m b/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m
new file mode 100644
index 0000000000000000000000000000000000000000..b07b39d257eab1c73dee0921502a3ae45f6affeb
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/rafos.m
@@ -0,0 +1,186 @@
+function [ boolean ] = rafos( p_nemo_data, p_config )
+%RAFOS Plots the RAFOS data. of the given nemo floats.
+%   
+%   Parameters:
+%   p_nemo_data:        The nemo data which will be plotted.
+%   p_config:           The nemo configuration.
+%   
+%   Returns:
+%   boolean:            True if successful.
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check %TODO
+
+%% Get config parameter
+% get plot details
+width = p_config.PLOT_RAFOS.image_width;
+height = p_config.PLOT_RAFOS.image_height;
+paperunit = p_config.PLOT_RAFOS.image_paperunit;
+image_type = p_config.PLOT_RAFOS.image_type;
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_RAFOS.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_RAFOS.storage_directory_fig);
+ylabel_fontsize = p_config.PLOT_RAFOS.ylabel_fontsize;
+xlabel_fontsize = p_config.PLOT_RAFOS.xlabel_fontsize;
+axis_fontsize = p_config.PLOT_RAFOS.axis_fontsize;
+title_fontsize = p_config.PLOT_RAFOS.title_fontsize;
+
+if (~isdir(directory))
+    mkdir(directory);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Collect required data
+
+switch (class(p_nemo_data{1}))
+    case 'struct' % only one float is to be processed
+        rafos_names = p_nemo_data{1}.RAFOS_VALUES_FORMAT;
+        boolean = plot_float(p_nemo_data);
+    case 'cell' % more than one float has to be processed
+        rafos_names = p_nemo_data{1}{1}.RAFOS_VALUES_FORMAT;
+        boolean = all(cellfun(@plot_float, p_nemo_data));
+    otherwise
+        disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']);
+end
+
+    function boolean = plot_float(p_float)
+        boolean = false;
+        rafos_data = nemo.float.collect(p_float, 'RAFOS_VALUES', '', 'combine');
+        
+        if (isempty(rafos_data))
+            boolean = true;
+            return
+        end
+        
+        
+        plot_c = [];
+        plot_y = [];
+        plot_x = [];
+        plot_status = [];
+        for i = 8:2:18
+            rafos_correlation = rafos_data(:, i);
+            
+            plot_c = [plot_c; rafos_correlation];
+            plot_y = [plot_y; rafos_data(:, i + 1)];
+            plot_x = [plot_x; datenum(rafos_data(:, 2:7))];
+            plot_status = [plot_status; rafos_data(:, 1)];
+        end
+
+        % sort rows by color
+        sorted_plot = [plot_x, plot_y, plot_c];
+        sorted_plot = flipud(sortrows(sorted_plot, 3));
+        plot_x = sorted_plot(:, 1);
+        plot_y = sorted_plot(:, 2);
+        plot_c = sorted_plot(:, 3);
+        clear sorted_plot;
+        
+        
+        % initialize figure
+        figure_id = figure('Visible', 'off'); % create figure
+        %figure_id = figure(); % create figure
+        set(figure_id,'InvertHardcopy','on');
+        set(figure_id,'PaperUnits', paperunit);
+        papersize = get(figure_id, 'PaperSize');
+        left = (papersize(1)- width)/2;
+        bottom = (papersize(2)- height)/2;
+        myfiguresize = [left, bottom, width, height];
+        set(figure_id,'PaperPosition', myfiguresize);
+        
+        % plot data
+        sub(1) = subplot(5, 1, 1:4); % travel time
+        hold on
+        box on
+        grid on
+        % get required information about x-axis limits and ticks
+        x_diff = max(plot_x) - min(plot_x);
+        set(gca, 'XLimMode', 'manual', 'XLim', [min(plot_x), max(plot_x)]);
+        if (x_diff < 30)
+            set(gca, 'XTick', [min(plot_x):5:max(plot_x)]);
+        elseif (x_diff < 365)
+            set(gca, 'XTick', [min(plot_x):30:max(plot_x)]);
+        else
+            set(gca, 'XTick', [min(plot_x):365:max(plot_x)]);
+        end
+        
+        s = scatter3(plot_x, plot_y, plot_c, 12, plot_c, 'filled');
+        view(0, 90);
+        c = colorbar;
+        caxis([0 255]);
+        colormap(flipud(colormap));
+        c.Label.String = 'Correlation Height (ch)';
+        
+        ylim(p_config.PLOT_RAFOS.ylim);
+        ylabel('RAFOS Time of Arrival [s]', 'FontSize', ylabel_fontsize);
+%         xlabel('Date of profile', 'FontSize', xlabel_fontsize);
+        if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0)
+            deployment_string = ' (Arctic)';
+        else
+            deployment_string = ' (Antarctica)';
+        end
+        title(sub(1), [ 'Float ' ...
+               num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+               ' @ ' ...
+               datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+               deployment_string ...
+             ], 'FontSize', title_fontsize);
+        datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits');
+        set(gca, 'fontsize', axis_fontsize)
+        clear s
+        hold off
+        
+        
+        sub(2) = subplot(5, 1, 5); % rafos status
+        hold on
+        box on
+        grid on
+        % get required information about x-axis limits and ticks
+        x_diff = max(plot_x) - min(plot_x);
+        set(gca, 'XLimMode', 'manual', 'XLim', [min(plot_x), max(plot_x)]);
+        if (x_diff < 30)
+            set(gca, 'XTick', [min(plot_x):5:max(plot_x)]);
+        elseif (x_diff < 365)
+            set(gca, 'XTick', [min(plot_x):30:max(plot_x)]);
+        else
+            set(gca, 'XTick', [min(plot_x):365:max(plot_x)]);
+        end
+        set(gca, 'YTickMode', 'manual', 'YLimMode', 'manual', ...
+            'YTick', [0:2:10], 'YLim', [0, 10]);
+        
+        s = stairs(datenum(rafos_data(:, 2:7)), rafos_data(:, 1), ...
+            'Marker', 'o');
+        s.DisplayName = rafos_names{1};
+        
+        clear s
+%         title(sub(2), 'RAFOS status', 'FontSize', p_config.PLOT_RAFOS.title_fontsize);
+        datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits');
+        ylim([0 8]);
+        ylabel('Status', 'FontSize', ylabel_fontsize);
+        xlabel('Date of profile', 'FontSize', xlabel_fontsize);
+        set(gca, 'fontsize', axis_fontsize)
+        clear a
+        hold off
+        
+        print(figure_id, ...
+         [directory p_config.PLOT_RAFOS.image_filename ...
+           num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ...
+         , image_type);
+     
+        if (p_config.PLOT_RAFOS.save_figure)
+            set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+            saveas(figure_id, ...
+                [directory_fig ...
+                 p_config.PLOT_RAFOS.figure_filename ...
+                 num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]);
+        end
+     
+        close(figure_id);
+        boolean = true;
+    end
+
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m
new file mode 100644
index 0000000000000000000000000000000000000000..e7426bf5125e1a1e76684466df4a03482adc6856
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal.m
@@ -0,0 +1,191 @@
+function [ boolean ] = temp_sal( p_nemo_data, p_config )
+%TEMP_SAL Generates a temperature/salinity plot for every float given.
+%   Generates a plot with two subplots, depth on time.
+%   The colors of the first plot are given by the temperature, the second
+%   one shows the salinity.
+%
+%   Parameters:
+%   p_nemo_data:        The nemo data.
+%   p_config:           The nemo configuration.
+%
+%   Returns:
+%   boolean:            False on fail.
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check %TODO
+
+%% Get config parameter
+% get plot details
+width = p_config.PLOT_TEMP_SAL.image_width;
+height = p_config.PLOT_TEMP_SAL.image_height;
+paperunit = p_config.PLOT_TEMP_SAL.image_paperunit;
+image_type = p_config.PLOT_TEMP_SAL.image_type;
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL.storage_directory_fig);
+ylabel_fontsize = p_config.PLOT_TEMP_SAL.ylabel_fontsize;
+xlabel_fontsize = p_config.PLOT_TEMP_SAL.ylabel_fontsize;
+axis_fontsize = p_config.PLOT_TEMP_SAL.axis_fontsize;
+title_fontsize = p_config.PLOT_TEMP_SAL.title_fontsize;
+
+if (~isdir(directory))
+    mkdir(directory);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Collect required data
+
+switch (class(p_nemo_data{1}))
+    case 'struct' % only one float is to be processed
+        boolean = plot_float(p_nemo_data);
+    case 'cell' % more than one float has to be processed
+        boolean = all(cellfun(@plot_float, p_nemo_data));
+    otherwise
+        disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']);
+end
+
+    function boolean = plot_float(p_float)
+        boolean = false;
+        profiles_data = nemo.float.collect(p_float, 'PROFILE_DATA', '');
+        profiles_data_combined = nemo.float.collect(p_float, 'PROFILE_DATA', '',  'combine');
+        max_pressure = max(profiles_data_combined(:, 7));
+        profiles_datetime = nemo.float.collect(p_float, ...
+                                               'SECOND_ORDER_INFORMATION', ...
+                                               'best_guess_datevec');
+        profiles_datetime = datenum(profiles_datetime(:, 1:6));
+        if (isempty(profiles_data))
+            boolean = true;
+            return
+        end
+        
+        % initialize figure
+        figure_id = figure('Visible', 'Off');
+        set(figure_id,'InvertHardcopy','on');
+        set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]);
+        papersize = get(figure_id, 'PaperSize');
+        left = (papersize(1) - width)/2;
+        bottom = (papersize(2) - height)/2;
+        myfiguresize = [left, bottom, width, height];
+        set(figure_id,'PaperPosition', myfiguresize);
+        
+        % Temperature plot
+        sub(1) = subplot(2, 1, 1);
+        hold on
+        box on
+        grid on
+        set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 max_pressure]);
+        set(gca, 'fontsize', axis_fontsize)
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        % get required information about x-axis limits and ticks
+        x_diff = max(profiles_datetime) - min(profiles_datetime);
+        set(gca, 'XLimMode', 'auto');
+        if (x_diff < 30)
+            set(gca, 'XTick', [min(profiles_datetime):5:max(profiles_datetime)]);
+        elseif (x_diff < 365)
+            set(gca, 'XTick', [min(profiles_datetime):30:max(profiles_datetime)]);
+        else
+            set(gca, 'XTick', [min(profiles_datetime):365:max(profiles_datetime)]);
+        end
+        time_combined = [];
+        for i = 1:size(profiles_datetime, 1)
+            temperature = profiles_data{i}(:, 5);
+            pressure = profiles_data{i}(:, 7);
+            % erase all invalid values
+            valid_points = (pressure >= 0) & (pressure <= 2000) & (temperature < 10);
+            temperature = temperature(valid_points);
+            pressure = pressure(valid_points);
+            time = repmat(profiles_datetime(i, :), size(pressure, 1), 1);
+            time_combined = [time_combined; time];
+            scatter(time, pressure, 45, temperature, 'filled');
+        end
+        datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits');
+%         xlabel('Date of profile', 'FontSize', xlabel_fontsize);
+
+
+        c = colorbar;
+        cmocean('thermal');
+        %colormap(gca, 'jet');
+        %caxis([-5, 15]);
+        c.Label.String = ['Temperature [' char(176) 'C]'];
+        clear c
+        % create main title
+        if (p_float{1}.SECOND_ORDER_INFORMATION.best_guess_position_lat > 0)
+            deployment_string = ' (Arctic)';
+        else
+            deployment_string = ' (Antarctica)';
+        end
+        t = title(...
+             [ 'Float ' ...
+               num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+               ' @ ' ...
+               datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+               deployment_string ...
+             ]);
+        set(t, 'FontSize', title_fontsize);
+        clear t
+        hold off
+        
+        % Salinity plot
+        sub(2) = subplot(2, 1, 2);
+        hold on
+        box on
+        grid on
+        set(gca, 'YLimMode', 'manual', 'YDir', 'reverse', 'YLim', [0 max_pressure]);
+        set(gca, 'fontsize', axis_fontsize)
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        xlabel('Date of profile', 'FontSize', xlabel_fontsize);
+        
+        % get required information about x-axis limits and ticks
+        x_diff = max(profiles_datetime) - min(profiles_datetime);
+        set(gca, 'XLimMode', 'auto');
+        if (x_diff < 30)
+            set(gca, 'XTick', [min(profiles_datetime):5:max(profiles_datetime)]);
+        elseif (x_diff < 365)
+            set(gca, 'XTick', [min(profiles_datetime):30:max(profiles_datetime)]);
+        else
+            set(gca, 'XTick', [min(profiles_datetime):365:max(profiles_datetime)]);
+        end
+        for i = 1:size(profiles_datetime, 1)
+            salinity = profiles_data{i}(:, 9);
+            pressure = profiles_data{i}(:, 7);
+            % erase all invalid values
+            valid_points = (pressure >= 0) & (pressure <= 2000) & (salinity > 10);
+            time = repmat(profiles_datetime(i, :), size(pressure(valid_points), 1), 1);
+            scatter(time, ...
+                    pressure(valid_points), 45, salinity(valid_points), 'filled');
+        end
+        datetick('x','yyyy-mm-dd', 'keepticks', 'keeplimits');
+        c = colorbar;
+        cmocean('haline');
+        %colormap(gca, [0:0.001:1; repmat(0, 1, 1001); fliplr(0:0.001:1)]');
+        %caxis([33, 36]);
+        c.Label.String = 'Salinity';
+        hold off
+
+         
+        set(gcf, 'Renderer', 'painters');
+        print(figure_id, ...
+         [directory p_config.PLOT_TEMP_SAL.image_filename ...
+           num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ...
+         , image_type);
+     
+        if (p_config.PLOT_TEMP_SAL.save_figure)
+            set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+            saveas(figure_id, ...
+                [directory_fig ...
+                 p_config.PLOT_TEMP_SAL.figure_filename ...
+                 num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]);
+        end
+        
+        close(figure_id);
+     
+        clear figure_id f
+        boolean = true;
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m
new file mode 100644
index 0000000000000000000000000000000000000000..c2784035e989c0a18c9b926bfa47f5a76c256197
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/temp_sal_ptemp.m
@@ -0,0 +1,211 @@
+function [ boolean ] = temp_sal_ptemp( p_nemo_data, p_config )
+%TEMP_SAL_PTEMP Plots the temperature, salinity and the potential temperature.
+%   
+%   Parameters:
+%   p_nemo_data:        The nemo data to be plotted.
+%   p_config:           The nemo configuration.
+%
+%   Returns:
+%   boolean:            True if successful.
+
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check %TODO
+
+%% Get config parameter
+% get plot details
+width = p_config.PLOT_TEMP_SAL_PTEMP.image_width;
+height = p_config.PLOT_TEMP_SAL_PTEMP.image_height;
+paperunit = p_config.PLOT_TEMP_SAL_PTEMP.image_paperunit;
+image_type = p_config.PLOT_TEMP_SAL_PTEMP.image_type;
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TEMP_SAL_PTEMP.storage_directory_fig);
+ylabel_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.ylabel_fontsize;
+xlabel_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.xlabel_fontsize;
+axis_fontsize = p_config.PLOT_TEMP_SAL_PTEMP.axis_fontsize;
+
+if (~isdir(directory))
+    mkdir(directory);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Collect required data
+
+switch (class(p_nemo_data{1}))
+    case 'struct' % only one float is to be processed
+        boolean = plot_float(p_nemo_data);
+    case 'cell' % more than one float has to be processed
+        boolean = all(cellfun(@plot_float, p_nemo_data));
+    otherwise
+        disp([mfilename ': Class ' class(p_nemo_data{1}) ' not supported for plotting!']);
+end
+
+    function boolean = plot_float(p_float)
+        boolean = false;
+        profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', '');
+        combined_profile_data = nemo.float.collect(p_float, 'PROFILE_DATA', '', 'combine');
+        best_guess_lat = nemo.float.collect(p_float, 'SECOND_ORDER_INFORMATION', 'best_guess_position_lat');
+        best_guess_lon = nemo.float.collect(p_float, 'SECOND_ORDER_INFORMATION', 'best_guess_position_lon');
+        
+        max_pressure = max(combined_profile_data(:, 7));
+        max_salinity = max(combined_profile_data(:, 9));
+        max_temperature = max(combined_profile_data(:, 5));
+        min_pressure = min(combined_profile_data(:, 7));
+        min_salinity = min(combined_profile_data(:, 9));
+        min_temperature = min(combined_profile_data(:, 5));
+        
+        if (isempty(profile_data))
+            boolean = true;
+            return
+        end
+        
+        % initialize figure
+        figure_id = figure('Visible', 'Off');
+        set(figure_id,'InvertHardcopy','on');
+        set(figure_id,'PaperUnits', paperunit, 'Position', [ 0 0 width height]);
+        papersize = get(figure_id, 'PaperSize');
+        left = (papersize(1) - width)/2;
+        bottom = (papersize(2) - height)/2;
+        myfiguresize = [left, bottom, width, height];
+        set(figure_id,'PaperPosition', myfiguresize);
+        
+        % Salinity plot
+        sub(1) = subplot(1, 3, 1);
+        hold on
+        set(gca, 'YLimMode', 'manual', ...
+                 'YLim', [min_pressure max_pressure], ...
+                 'YTick', [min_pressure:200:max_pressure], ...
+                 'YDir', 'reverse', ...
+                 'XLimMode', 'manual', ...
+                 'XLim', [min_salinity max_salinity], ...
+                 'XTickMode', 'auto');
+        for i = 1:length(profile_data)
+            pressure = profile_data{i}(:, 7);
+            salinity = profile_data{i}(:, 9);
+            plot(salinity, pressure, ...
+                 'color', [0 0 1] * i/length(profile_data));
+        end
+        box on
+        grid on
+        hold off
+        
+        % create main title
+        t = title(...
+             [ 'Float ' ...
+               num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+               ' @ ' ...
+               datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+             ]);
+        set(t, 'FontSize', 18);
+        clear t
+        xlabel('Salinity [PSU]', 'FontSize', xlabel_fontsize);
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        set(gca, 'fontsize', axis_fontsize);
+%         set(gca, 'YDir', 'reverse', 'YTick', [0:2000]);
+%         ylim([-2000 0]);
+        
+%         set(gca, 'YLimMode', 'manual', 'YLim', [-2000 0], 'YTick', [-2000:200:0]);
+        
+        clear salinity pressure a
+        
+        % Temperature plot
+        sub(2) = subplot(1, 3, 2);
+        hold on
+        set(gca, 'YLimMode', 'manual', ...
+                 'YLim', [min_pressure max_pressure], ...
+                 'YTick', [min_pressure:200:max_pressure], ...
+                 'YDir', 'reverse', ...
+                 'XLimMode', 'manual', ...
+                 'XLim', [min_temperature, max_temperature], ...
+                 'XTickMode', 'auto');
+        for i = 1:length(profile_data)
+            pressure = profile_data{i}(:, 7);
+            temperature = profile_data{i}(:, 5);
+            plot(temperature, pressure, ...
+                 'color', [1 0 0] * i/length(profile_data));
+        end
+        xlabel('Temperature [°C]', 'FontSize', xlabel_fontsize);
+        ylabel('Depth [m]', 'FontSize', ylabel_fontsize);
+        set(gca, 'fontsize', axis_fontsize);
+        box on
+        grid on
+        hold off
+        clear pressure temperature a
+        
+        % Potential temperature plot
+        sub(3) = subplot(2, 3, 3);
+        hold on
+        set(gca, 'XLimMode', 'manual', ...
+                 'XLim', [min_salinity max_salinity], ...
+                 'XTick', [min_salinity:max_salinity], ...
+                 'XTickMode', 'auto', ...
+                 'YLimMode', 'manual', ...
+                 'YTickMode', 'auto', ...
+                 'YLim', [min_temperature, max_temperature]);
+        for i = 1:length(profile_data)
+            pressure = profile_data{i}(:, 7);
+            temperature = profile_data{i}(:, 5);
+            salinity = profile_data{i}(:, 9);
+            plot(salinity, sw_ptmp(salinity, temperature, pressure, 0), ...
+                         'Linestyle', 'none', ...
+                         'color', [0 1 0] * i/length(profile_data), ...
+                         'marker','.' ...
+                );
+        end
+        ylabel('\theta (Potential temperature) [°C]', 'FontSize', xlabel_fontsize);
+        xlabel('Salinity [PSU]', 'FontSize', ylabel_fontsize);
+        set(gca, 'fontsize', axis_fontsize);
+        box on
+        grid on
+        hold off
+        clear a
+        
+        sub(4) = subplot(2, 3, 6);
+        hold on
+        latlim = [-80 80];
+        lonlim = [-90 70];
+        worldmap(latlim,lonlim);
+        setm(gca, 'MLabelParallel', 'south')
+        if (~exist('map', 'var'))
+            map = load('coast');
+        end
+        plotm(map.lat,map.long,'k');
+        plot3m(best_guess_lat,best_guess_lon,150,'marker','.','markersize',8,'color',[0 1 0],'linestyle','none');
+        xlabel('Longitude', 'FontSize', xlabel_fontsize);
+        ylabel('Latitude', 'FontSize', ylabel_fontsize);
+        set(gca, 'fontsize', axis_fontsize);
+        box on
+        grid on
+        hold off
+        clear a
+        
+         
+        set(gcf, 'Renderer', 'painters');
+        print(figure_id, ...
+         [directory p_config.PLOT_TEMP_SAL_PTEMP.image_filename ...
+           num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)] ...
+         , image_type);
+
+        if (p_config.PLOT_TEMP_SAL_PTEMP.save_figure)
+            set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+            saveas(figure_id, ...
+                [directory_fig ...
+                 p_config.PLOT_TEMP_SAL_PTEMP.figure_filename ...
+                 num2str(p_float{1}.FLOAT_IDENTIFICATION.float_serial_number)]);
+        end
+
+        close(figure_id);
+        clear figure_id f
+        boolean = true;
+        
+    end
+
+
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m b/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m
new file mode 100644
index 0000000000000000000000000000000000000000..3c53bbd427be919cb8fa9c823d1739d2f9c60b36
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+plot/timing_information.m
@@ -0,0 +1,507 @@
+function [ boolean ] = timing_information( p_nemo_data, p_config )
+%TIMING_INFORMATION Plots clock details.
+%
+%   Returns:
+%   boolean: False if something went wrong during the plotting process.
+%
+%   Parameters:
+%   p_nemo_data:      Struct, containing all float data (load with nemo.load.data()).
+%   p_config:         Global configuration file. Load using nemo.load.config().
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_data) || ~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Setup directories
+directory = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TIMING_INFORMATION.storage_directory_image);
+directory_fig = fullfile(p_config.GENERAL.storage_directory_plots, p_config.PLOT_TIMING_INFORMATION.storage_directory_fig);
+image_type = p_config.PLOT_TIMING_INFORMATION.image_type;
+if ~exist(directory, 'dir')
+    system(['mkdir ' directory]);
+end
+if ~exist(directory_fig, 'dir')
+    mkdir(directory_fig);
+end
+
+%% Process every float
+
+% create dummy legend item to make the boxes be all of the same size
+
+float_count = length(p_nemo_data);
+
+for o_float = 1:float_count
+    
+    
+    gps_profile_data = struct();
+    all_profile_data = struct();
+    all_profile_data.profiles = p_nemo_data{o_float};
+    all_profile_data.best_guess_position_src = nemo.float.collect(p_nemo_data{o_float}, 'SECOND_ORDER_INFORMATION', 'best_guess_position_src');
+    all_profile_data.best_guess_datevec = nemo.float.collect(p_nemo_data{o_float}, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec');
+    
+    gps_profile_data.profiles = {};
+    for i = 1:length(all_profile_data.best_guess_position_src)
+        if (strcmp(all_profile_data.best_guess_position_src{i}, 'gps'))
+            gps_profile_data.profiles{end + 1} = p_nemo_data{o_float}{i};
+        end
+    end
+%     
+%     if isempty(gps_profile_data.profiles)
+%         continue
+%     end
+    
+    gps_profile_data.best_guess_datevec = nemo.float.collect(gps_profile_data.profiles, 'SECOND_ORDER_INFORMATION', 'best_guess_datevec');
+    
+    all_profile_data.start_date = p_nemo_data{o_float}{end}.OVERALL_MISSION_INFORMATION.start_date;
+    all_profile_data.start_time = p_nemo_data{o_float}{end}.OVERALL_MISSION_INFORMATION.start_time;
+    gps_profile_data.surface_gps_data = nemo.float.collect(gps_profile_data.profiles, 'SURFACE_GPS_DATA');
+    
+    plot_data = struct();
+    % required data for plot 1
+    
+    % SURFACESTART - GPSFix
+    if ~isempty(gps_profile_data.profiles)
+        plot_data.surfaceStart_GPS = struct();
+        gps_profile_data.xmit_surface_start_datevec = nemo.float.collect(gps_profile_data.profiles, 'xmitDatevec', 'xmit_surface_start_time');
+        plot_data.surfaceStart_GPS.y = ( ...
+            datenum(gps_profile_data.xmit_surface_start_datevec) - ...
+            datenum(gps_profile_data.surface_gps_data(:, 8:13)) ...
+            ) * 86400;
+        plot_data.surfaceStart_GPS.x = datenum(gps_profile_data.best_guess_datevec);
+    end
+    
+    % RTC0015 - GPSFix
+    if ~(isempty(gps_profile_data.profiles))
+        gps_profile_data.profile_data = nemo.float.collect(gps_profile_data.profiles, 'PROFILE_DATA');
+        plot_data.RTC0015_GPSFix = struct();
+        % get values near to 15m depth
+        [~, indices] = cellfun(@(x) find_nearest_value_to(x(:, 7), 15), gps_profile_data.profile_data, 'UniformOutput', false);
+        time_15m = cellfun(@(x,y) x(y, 2:4), gps_profile_data.profile_data, indices, 'UniformOutput', false);
+        time_15m = cell2mat(time_15m');
+        gps_profile_data.RTC0015_datevec = gps_profile_data.surface_gps_data(:, 8:13);
+        gps_profile_data.RTC0015_datevec(:, 4:6) = time_15m;
+        tmp_subtract_one_day = (gps_profile_data.surface_gps_data(:, 11) >= 21 & gps_profile_data.surface_gps_data(:, 11) <= 23) ...
+            & (time_15m(:, 1) >= 0 & time_15m(:, 1) <= 3);
+        tmp_add_one_day = (gps_profile_data.surface_gps_data(:, 11) >= 0 & gps_profile_data.surface_gps_data(:, 11) <= 3) ...
+            & (time_15m(:, 1) >= 21 & time_15m(:, 1) <= 23);
+        
+        if (any(tmp_subtract_one_day))
+            % GPS is more trustworthy, so reduce the RTC date by 1
+            tmp_indices = find(tmp_subtract_one_day);
+            for i = 1:length(tmp_indices)
+                gps_profile_data.RTC0015_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([gps_profile_data.RTC0015_datevec(tmp_indices(i), 1:3), time_15m(tmp_indices(i), :)]), -1, 'day'));
+            end
+        end
+        if (any(tmp_add_one_day))
+            % same here, but now add 1 day
+            tmp_indices = find(tmp_add_one_day);
+            for i = 1:length(tmp_indices)
+                gps_profile_data.RTC0015_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([gps_profile_data.RTC0015_datevec(tmp_indices(i), 1:3), time_15m(tmp_indices(i), :)]), +1, 'day'));
+            end
+        end
+        clear tmp_indices tmp_subtract_one_day tmp_add_one_day
+        
+        plot_data.RTC0015_GPSFix.y = ( ...
+            datenum(gps_profile_data.RTC0015_datevec) - ...
+            datenum(gps_profile_data.surface_gps_data(:, 8:13)) ...
+            ) * 86400;
+        plot_data.RTC0015_GPSFix.x = datenum(gps_profile_data.best_guess_datevec);
+    end
+    
+    
+    % SURFACESTART - IRIDIUM
+    if ~isempty(gps_profile_data.profiles)
+    plot_data.surfaceStart_Iridium = struct();
+    gps_profile_data.iridium_data = nemo.float.collect(gps_profile_data.profiles, 'IRIDIUM_DATA');
+    plot_data.surfaceStart_Iridium.y = ( ...
+        datenum(gps_profile_data.xmit_surface_start_datevec) - ...
+        datenum(gps_profile_data.iridium_data(:, 4:9)) ...
+        ) * 86400;
+    plot_data.surfaceStart_Iridium.x = datenum(gps_profile_data.best_guess_datevec);
+    end
+    
+    % ASCENTSTART - RTC2000
+    plot_data.ascentStart_RTC2000 = struct();
+    all_profile_data.xmit_ascent_start_datevec = nemo.float.collect(all_profile_data.profiles, 'xmitDatevec', 'xmit_ascent_start_time');
+    all_profile_data.profile_data = nemo.float.collect(all_profile_data.profiles, 'PROFILE_DATA');
+    all_profile_data.profile_data_2000 = cellfun(@(x) x(end, 2:4), all_profile_data.profile_data, 'UniformOutput', false);
+    all_profile_data.profile_data_2000 = cell2mat(all_profile_data.profile_data_2000');
+    all_profile_data.profile_data_2000_datevec = datevec(datetime([all_profile_data.xmit_ascent_start_datevec(:, 1:3), all_profile_data.profile_data_2000]));
+   
+    tmp_subtract_one_day = (all_profile_data.profile_data_2000(:, 1) >= 21 & all_profile_data.profile_data_2000(:, 1) <= 23) ...
+        & (all_profile_data.xmit_ascent_start_datevec(:, 4) >= 0 & all_profile_data.xmit_ascent_start_datevec(:, 4) <= 3);
+    tmp_add_one_day = (all_profile_data.profile_data_2000(:, 1) >= 0 & all_profile_data.profile_data_2000(:, 1) <= 3) ...
+        & (all_profile_data.xmit_ascent_start_datevec(:, 4) >= 21 & all_profile_data.xmit_ascent_start_datevec(:, 4) <= 23);
+    
+    if (any(tmp_subtract_one_day))
+        % RTC is more trustworthy, so reduce the software date by 1
+        tmp_indices = find(tmp_subtract_one_day);
+        for i = 1:length(tmp_indices)
+            all_profile_data.profile_data_2000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_ascent_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_2000(tmp_indices(i), :)]), -1, 'day'));
+        end
+    end
+    if (any(tmp_add_one_day))
+        % same here, but now add 1 day
+        tmp_indices = find(tmp_add_one_day);
+        for i = 1:length(tmp_indices)
+            all_profile_data.profile_data_2000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_ascent_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_2000(tmp_indices(i), :)]), +1, 'day'));
+        end
+    end
+    clear tmp_indices tmp_subtract_one_day tmp_add_one_day
+    plot_data.ascentStart_RTC2000.y = ( ...
+        datenum(all_profile_data.xmit_ascent_start_datevec) - ...
+        datenum(all_profile_data.profile_data_2000_datevec) ...
+        ) * 86400;
+    plot_data.ascentStart_RTC2000.x = datenum(all_profile_data.best_guess_datevec);
+    
+    % SURFACESTART - RTC0000
+    plot_data.surfaceStart_RTC0000 = struct();
+    all_profile_data.xmit_surface_start_datevec = nemo.float.collect(all_profile_data.profiles, 'xmitDatevec', 'xmit_surface_start_time');
+    all_profile_data.profile_data_0000 = cellfun(@(x) x(1, 2:4), all_profile_data.profile_data, 'UniformOutput', false);
+    all_profile_data.profile_data_0000 = cell2mat(all_profile_data.profile_data_0000');
+    all_profile_data.profile_data_0000_datevec = datevec(datetime([all_profile_data.xmit_surface_start_datevec(:, 1:3), all_profile_data.profile_data_0000]));
+        
+    tmp_subtract_one_day = (all_profile_data.profile_data_0000(:, 1) >= 21 & all_profile_data.profile_data_0000(:, 1) <= 23) ...
+        & (all_profile_data.xmit_surface_start_datevec(:, 4) >= 0 & all_profile_data.xmit_surface_start_datevec(:, 4) <= 3);
+    tmp_add_one_day = (all_profile_data.profile_data_0000(:, 1) >= 0 & all_profile_data.profile_data_0000(:, 1) <= 3) ...
+        & (all_profile_data.xmit_surface_start_datevec(:, 4) >= 21 & all_profile_data.xmit_surface_start_datevec(:, 4) <= 23);
+    
+    if (any(tmp_subtract_one_day))
+        % RTC is more trustworthy, so reduce the software date by 1
+        tmp_indices = find(tmp_subtract_one_day);
+        for i = 1:length(tmp_indices)
+            all_profile_data.profile_data_0000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_surface_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_0000(tmp_indices(i), :)]), -1, 'day'));
+        end
+    end
+    if (any(tmp_add_one_day))
+        % same here, but now add 1 day
+        tmp_indices = find(tmp_add_one_day);
+        for i = 1:length(tmp_indices)
+            all_profile_data.profile_data_0000_datevec(tmp_indices(i), :) = datevec(addtodate(datenum([all_profile_data.xmit_surface_start_datevec(tmp_indices(i), 1:3), all_profile_data.profile_data_0000(tmp_indices(i), :)]), +1, 'day'));
+        end
+    end
+    clear tmp_indices tmp_subtract_one_day tmp_add_one_day
+    plot_data.surfaceStart_RTC0000.y = ( ...
+        datenum(all_profile_data.xmit_surface_start_datevec) - ...
+        datenum(all_profile_data.profile_data_0000_datevec) ...
+        ) * 86400;
+    plot_data.surfaceStart_RTC0000.x = datenum(all_profile_data.best_guess_datevec);
+    
+    
+    % required data for plot 2
+    % get values near to 15m depth
+    clear indices
+    [~, indices] = cellfun(@(x) find_nearest_value_to(x(:, 7), 15), all_profile_data.profile_data, 'UniformOutput', false);
+    all_time_15m = cellfun(@(x,y) x(y, 2:4), all_profile_data.profile_data, indices, 'UniformOutput', false);
+    all_time_15m = cell2mat(all_time_15m');
+    
+    % required data for plot 3
+    % GPS - IRIDIUM
+    if ~isempty(gps_profile_data.profiles)
+    plot_data.GPS_Iridium = struct();
+    plot_data.GPS_Iridium.y = ( ...
+        datenum(gps_profile_data.surface_gps_data(:, 8:13)) - ...
+        datenum(gps_profile_data.iridium_data(:, 4:9)) ...
+        ) * 86400;
+    plot_data.GPS_Iridium.x = datenum(gps_profile_data.best_guess_datevec);
+    
+    % GPS - GPSRTC
+    plot_data.GPS_GPSRTC = struct();
+    plot_data.GPS_GPSRTC.y = ( ...
+        datenum(gps_profile_data.surface_gps_data(:, 8:13)) - ...
+        datenum(gps_profile_data.surface_gps_data(:, 2:7)) ...
+        ) * 86400;
+    plot_data.GPS_GPSRTC.x = datenum(gps_profile_data.best_guess_datevec);
+    end
+    
+    % get plot details
+    width = p_config.PLOT_TIMING_INFORMATION.image_width;
+    height = p_config.PLOT_TIMING_INFORMATION.image_height;
+    paperunit = p_config.PLOT_TIMING_INFORMATION.image_paperunit;
+    
+    % plot collected data
+    
+    figure_id = figure('Visible', 'off'); % create figure
+    % initialize figure
+    set(figure_id,'InvertHardcopy','on');
+    set(figure_id,'PaperUnits', paperunit);
+    papersize = get(figure_id, 'PaperSize');
+    left = (papersize(1)- width)/2;
+    bottom = (papersize(2)- height)/2;
+    myfiguresize = [left, bottom, width, height];
+    set(figure_id,'PaperPosition', myfiguresize);
+    
+    clear height width left bottom papersize myfiguresize
+    
+    %set(gcf, 'Position', get(0,'ScreenSize'), 'color', [1 1 1]);
+    sub = {};
+    figure_count = 3;
+    
+    sub{1} = subplot(figure_count,1,1);
+    hold on
+
+    %yyaxis left
+    if ~isempty(gps_profile_data.profiles)
+    plot(plot_data.surfaceStart_GPS.x, plot_data.surfaceStart_GPS.y, ...
+         'color', [1 0 0], ...
+         'marker', 'o', ...
+         'MarkerFaceColor', [1 0 0], ...
+         'MarkerEdgeColor', [1 0 0], ...
+         'linestyle', '-', ...
+         'linewidth', 1,...
+         'markersize', 5);
+    plot(plot_data.surfaceStart_Iridium.x, plot_data.surfaceStart_Iridium.y, ...
+         'color', [0 0.5 0], ...
+         'marker', 'x', ...
+         'MarkerEdgeColor', [0 0.5 0], ...
+         'linestyle', '-', ...
+         'linewidth', 1, ...
+         'markersize', 10);
+     plot(plot_data.RTC0015_GPSFix.x, plot_data.RTC0015_GPSFix.y, ...
+         'color', [0 0.5 1], ...
+         'marker', 'd', ...
+         'MarkerEdgeColor', [0 0.5 1], ...
+         'linestyle', '-', ...
+         'linewidth', 1, ...
+         'markersize', 10);
+    end
+     plot(plot_data.ascentStart_RTC2000.x, plot_data.ascentStart_RTC2000.y, ...
+         'color', [1 0 1], ...
+         'marker', '+', ...
+         'MarkerEdgeColor', [1 0 1], ...
+         'linestyle', '-', ...
+         'linewidth', 1, ...
+         'markersize', 10);
+     plot(plot_data.surfaceStart_RTC0000.x, plot_data.surfaceStart_RTC0000.y, ...
+         'color', [0 0 1], ...
+         'marker', 's', ...
+         'MarkerEdgeColor', [0 0 1], ...
+         'linestyle', '-', ...
+         'linewidth', 1, ...
+         'markersize', 10);
+
+
+    t = title([ 'Float ' ...
+          num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+          ' @ ' ...
+          datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+        ]);
+    set(t, 'FontSize', 18);
+    ylabel('Time differences [sec]');
+    if ~isempty(gps_profile_data.profiles)
+        leg1 = legend('SurfaceStart-GPSFix', 'SurfaceStart-IRIDIUM', 'RTC0015 - GPSFix', 'AscentStart-RTC2000', 'SurfaceStart-RTC0000');
+    else
+        leg1 = legend('AscentStart-RTC2000', 'SurfaceStart-RTC0000');
+    end
+    set(leg1, 'Location', 'eastoutside', 'Orientation', 'vertical');
+    grid on
+    box on
+    hold off
+    
+    sub{2} = subplot(figure_count,1,2);
+    hold on
+    ylabel('Time of day [HH:MM]');
+    
+    if ~isempty(gps_profile_data.profiles)
+        scatter(datenum(gps_profile_data.surface_gps_data(:, 8:13)), mod(datenum(gps_profile_data.surface_gps_data(:, 8:13)), 1), 20, ...
+            'marker', 'o', ...
+            'MarkerFaceColor', '1 0 0', ...
+            'MarkerEdgeColor', '1 0 0');
+        scatter(datenum(gps_profile_data.best_guess_datevec), mod(datenum(gps_profile_data.iridium_data(:, 4:9)), 1), 50, ...
+            'marker', 's', ...
+            'MarkerEdgeColor', '0 0.5 0');
+        
+    end
+    
+    scatter(datenum(all_profile_data.best_guess_datevec), mod(datenum([repmat([1 1 1970], size(all_time_15m, 1), 1), all_time_15m]), 1), 50, ...
+            'marker', '^', ...
+            'MarkerEdgeColor', '0 0.5 1');
+    scatter(datenum(all_profile_data.best_guess_datevec), mod(datenum(all_profile_data.xmit_surface_start_datevec), 1), 50, ...
+            'marker', '+', ...
+            'MarkerEdgeColor', '0 0 1');
+
+    set(gca, 'YLimMode', 'manual',...
+             'YLim', [0, 1], ...
+             'YTick', [0:6:24]./24);
+    datetick('y','HH:mm', 'keepticks');
+
+
+    if ~isempty(gps_profile_data.profiles)
+        legend('GPSFix', 'IRIDIUM', 'RTC0015', 'SurfaceStart');
+    else
+        legend('RTC0015', 'SurfaceStart');
+    end
+    legend('Location', 'eastoutside', 'Orientation', 'vertical');
+    box on
+    grid on
+    hold off
+    
+    sub{3} = subplot(figure_count,1,3);
+    hold on
+    if ~isempty(gps_profile_data.profiles)
+        plot(plot_data.GPS_Iridium.x, plot_data.GPS_Iridium.y, ...
+            'color', [0 0 1], ...
+            'marker', '+', ...
+            'MarkerEdgeColor', [0 0 1], ...
+            'linestyle', '-', ...
+            'linewidth', 1, ...
+            'markersize', 10);
+        
+        plot(plot_data.GPS_GPSRTC.x, plot_data.GPS_GPSRTC.y, ...
+            'color', [0 1 1], ...
+            'marker', '.', ...
+            'MarkerEdgeColor', [0 1 1], ...
+            'linestyle', '-', ...
+            'linewidth', 1, ...
+            'markersize', 10);
+        
+    else
+        dim = [0.5 0 .3 .3];
+        str = 'No data!';
+        a = annotation('textbox', dim, 'String', str, 'FitBoxToText', 'on');
+        a.FontSize = 16;
+        clear dim str a
+    end
+    
+    ylabel('Time differences [sec]');
+    legend('GPSFix-IRIDIUM', 'GPSFix-GPSRTC');
+    legend('Location', 'eastoutside', 'Orientation', 'vertical');
+    grid on
+    box on
+    hold off
+    
+    sub1_pos = get(sub{1}, 'Position');
+    sub2_pos = get(sub{2}, 'Position');
+    sub3_pos = get(sub{3}, 'Position');
+%     leg1_pos = get(leg1, 'Position');
+    set(sub{1}, 'Position', [sub1_pos(1:2) 0.6652 sub1_pos(4)]);
+    set(sub{2}, 'Position', [sub2_pos(1:2) 0.6652 sub2_pos(4)]);
+    set(sub{3}, 'Position', [sub3_pos(1:2) 0.6652 sub3_pos(4)]);
+    
+%     % align all legends
+%     leg1_pos = get(hleg1, 'position');
+%     leg2_pos = get(hleg2, 'position');
+%     leg3_pos = get(hleg3, 'position');
+%     legend_positions = sortrows([ leg1_pos; leg2_pos; leg3_pos ], 1);
+%     leg_width = max(legend_positions(:, 3));
+%     max_height = max(legend_positions(:, 4));
+% 
+%     set(hleg1, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg1_pos(2) leg_width max_height ]);
+%     set(hleg2, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg2_pos(2) leg_width max_height ]);
+%     set(hleg3, 'units', 'normalized', 'position', [ legend_positions(1, 1) leg3_pos(2) leg_width max_height ]);
+%     
+%     % set width of all plots to the same
+%     sub1_pos = get(sub{1}, 'position');
+%     sub2_pos = get(sub{2}, 'position');
+%     sub3_pos = get(sub{3}, 'position');
+%     sub_positions = sortrows([ sub1_pos; sub2_pos; sub3_pos ], 3);
+%     max_width = max(sub_positions(:, 3));
+%     set(sub{1}, 'units', 'normalized', 'position', [ sub1_pos(1:2) legend_positions(1, 1) sub1_pos(4) ]);
+%     set(sub{2}, 'units', 'normalized', 'position', [ sub2_pos(1:2) legend_positions(1, 1) sub2_pos(4) ]);
+%     set(sub{3}, 'units', 'normalized', 'position', [ sub3_pos(1:2) legend_positions(1, 1) sub3_pos(4) ]);
+    
+    sub = cellfun(@set_x_axis, sub, 'UniformOutput', false);
+    
+%     sub{3} = subplot(4, 1, 4);
+%     hold on
+%     %plot(combined_xmit_times);
+%     plot(xmit_profile_number, xmit_ascent_start_time_ori, 'marker', '+');
+%     plot(xmit_profile_number, xmit_ascent_end_time, 'marker', 'o');
+%     plot(xmit_profile_number, xmit_descent_start_time, 'marker', 'x');
+%     plot(xmit_profile_number, xmit_parking_start_time, 'marker', '*');
+%     plot(xmit_profile_number, xmit_upcast_start_time, 'marker', '+');
+%     plot(xmit_profile_number, xmit_surface_start_time, 'marker', '+');
+%     legend('ascentstart', 'ascentend', 'descentstart', 'parkingstart', 'upcaststart', 'surfacestart');
+%     box on
+%     grid on
+%     hold off
+    
+    
+    
+    % create main title
+%     heading = utils.suplabel(...
+%         [ 'Float ' ...
+%           num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number) ...
+%           ' @ ' ...
+%           datestr(datetime('now', 'Format', 'dd-MMM-yyyy HH:mm:SS')) ...
+%         ], 't');
+%     set(heading, 'FontSize', 18);
+    
+    % store plot
+    
+    image_filename = [p_config.PLOT_TIMING_INFORMATION.image_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    figure_filename = [p_config.PLOT_TIMING_INFORMATION.figure_filename ...
+        num2str(p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number)];
+    
+    if (p_config.PLOT_TIMING_INFORMATION.save_figure)
+        set(gcf,'CreateFcn','set(gcf,''Visible'',''on'')');
+        saveas(figure_id, ...
+               [directory_fig figure_filename]);
+     end
+    
+    
+    print(figure_id, [directory image_filename], image_type);
+    
+    close(figure_id);
+
+end
+
+boolean = true;
+
+    function current_plot = set_x_axis(current_plot)
+        
+        % get min and max datevec
+        min_datenum = min(datenum(all_profile_data.best_guess_datevec));
+        max_datenum = max(datenum(all_profile_data.best_guess_datevec));
+        if (min_datenum == max_datenum)
+            max_datenum = max_datenum + 1;
+        end
+        diff_datenum = max_datenum - min_datenum;
+        if (diff_datenum > 365)
+            x_tick = [min_datenum:365:max_datenum];
+        elseif (diff_datenum > 30)
+            x_tick = [min_datenum:30:max_datenum];
+        elseif (diff_datenum > 5)
+            x_tick = [min_datenum:5:max_datenum];
+        else
+            x_tick = [min_datenum:1:max_datenum];
+        end
+        if (x_tick(end) < max_datenum)
+            x_tick = [ x_tick max_datenum ];
+        end
+        x_tick_labels = cellstr(datestr(x_tick, 'yyyy-mm-dd'));
+        
+        if (~isempty(x_tick_labels))
+            x_tick_labels{end} = '';
+        end
+        
+        set(current_plot, 'XLimMode', 'manual', ...
+            'XLim', [min_datenum, max_datenum], ...
+            'XTick', x_tick, ...
+            'XTickLabel', x_tick_labels);
+        
+    end
+
+    function [value, index] = find_nearest_value_to(p_matrix, p_value)
+        [value, index] = min(abs(p_matrix - p_value));
+        if (length(value) > 1)
+            index = min(index);
+            value = p_matrix(index);
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m
new file mode 100644
index 0000000000000000000000000000000000000000..2209e7b906bdeb3f0fbf99f88afd83686cc2a9ce
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/add_software_profile_times2rtc.m
@@ -0,0 +1,100 @@
+function [ extended_nemo_data ] = add_software_profile_times2rtc( p_nemo_data )
+%ADD_SOFTWARE_PROFILE_TIMES_IN_RTC Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Parameter check
+if ~iscell(p_nemo_data)
+    disp([mfilename ': Please check your input parameter!']);
+end
+
+%% Variables to convert
+variable_names_to_convert = { ...
+    'xmit_descent_start_time', ...
+    'xmit_parking_start_time', ...
+    'xmit_upcast_start_time', ...
+    'xmit_ascent_end_time', ...
+    'xmit_surface_start_time' ...
+};
+
+%% Calculate datevec
+
+extended_nemo_data = cellfun(@(x) extend_float(x), p_nemo_data, 'UniformOutput', false);
+
+    function [ extended_float ] = extend_float(p_float)
+        extended_float = cellfun(@(x) extend_profile(x), p_float, 'UniformOutput', false);
+    end
+
+    function [ extended_profile ] = extend_profile(p_profile)
+        
+        extended_profile = p_profile;
+        if isempty(p_profile)
+            return
+        end
+        if ~isfield(extended_profile, 'SECOND_ORDER_INFORMATION')
+            extended_profile.SECOND_ORDER_INFORMATION = struct();
+        end
+        
+        % initialize variables
+        for i = 1:length(variable_names_to_convert)
+            extended_profile.SECOND_ORDER_INFORMATION.(variable_names_to_convert{i}) = NaN(1, 6);
+        end
+        extended_profile.SECOND_ORDER_INFORMATION.xmit_ascent_start_time = NaN(1, 6);
+        
+        % calculate software <-> rtc correction
+        % xmit_ascent_start_time + 10s = RTC @ 2000m
+        [rtc_datevec, software_datevec] = find_rtc_datevec(p_profile);
+        extended_profile.SECOND_ORDER_INFORMATION.xmit_ascent_start_time = datevec(datetime(rtc_datevec) - seconds(10));
+        
+        drift_rtc_software = etime(rtc_datevec, software_datevec) + 10;
+        for i = 1:length(variable_names_to_convert)
+            extended_profile.SECOND_ORDER_INFORMATION.(variable_names_to_convert{i}) = ...
+                datevec(datetime([ ...
+                    fliplr(extended_profile.OVERALL_MISSION_INFORMATION.start_date), ...
+                    extended_profile.OVERALL_MISSION_INFORMATION.start_time 0 ...
+                ]) ...
+                + seconds(extended_profile.PROFILE_TECHNICAL_DATA.(variable_names_to_convert{i})) ...
+                + seconds(drift_rtc_software));
+                
+        end
+        extended_profile.SECOND_ORDER_INFORMATION.difference_rtc_software = drift_rtc_software;
+        
+        
+    end
+
+    function [ rtc_datevec, software_datevec ] = find_rtc_datevec(profile)
+        software_datevec = ...
+            datevec( ...
+                datetime([ ...
+                    fliplr(profile.OVERALL_MISSION_INFORMATION.start_date), ...
+                    profile.OVERALL_MISSION_INFORMATION.start_time 0 ...
+                ]) ...
+                + seconds(profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ...
+        );
+        software_time = software_datevec(4:6);
+        % get internal clock (RTC) @ 2000m
+        rtc_time = profile.PROFILE_DATA( ...
+            profile.PROFILE_DATA(:, 7) == max(profile.PROFILE_DATA(:, 7)), ...
+            2:4);
+        % if there are multiple pressure levels get the one with the
+        % earliest time
+        rtc_time = sortrows(rtc_time, [1:3]);
+        if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty
+            rtc_datevec = NaN(1, 6);
+            software_datevec = NaN(1, 6);
+           return;
+        end
+        rtc_time = rtc_time(end, :);
+        
+        rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]));
+        
+        if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3))
+            % RTC is more trustworthy, so reduce the software date by 1
+            rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1));
+        elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23))
+            % same here, but now add 1 day
+            rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1));
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m
new file mode 100644
index 0000000000000000000000000000000000000000..eb6fa3df7200fa7fbacc1b969e1d91f50bda76d0
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+datetime/create_best_guess.m
@@ -0,0 +1,148 @@
+function [ nemo_data ] = create_best_guess( p_nemo_data )
+%CREATE_BEST_GUESS Estimates and writes the best guess datetime to the given structure.
+%   The Iridium time is only being taken if no GPS time is available, and it is
+%   not the first profile after a series with xmit_upcast_status="ICE DETECTION".
+%
+%   Priority that is being used:
+%   1) GPS time
+%   2) Iridium time
+%   3) Internal float clock time
+%   
+%   
+%   Returns: p_nemo_data with an additional struct containing the best
+%           guess datetime.
+%
+%   Parameters:
+%   p_nemo_data:      Struct, containing all floats that should be edited.
+
+%% Initialize return variables
+nemo_data = false;
+
+%% Parameter check
+parameter_error = false; % indicates if a parameter error was raised
+
+if (ischar(p_nemo_data)) % read profiles using read_profiles.m
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % read profiles
+        nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    else
+        nemo_data = p_nemo_data; % store nemo data in return variable
+    end
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by nemo.load.data!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Estimate best datetime
+
+% run through every float/profile
+nemo_data = p_nemo_data;
+float_count = length(p_nemo_data);
+for o_float = 1:float_count
+
+    current_float = p_nemo_data{o_float};
+    profile_count = length(current_float);
+
+    for o_profile_index = 1:profile_count
+        % get internal clock (software)
+        software_datevec = ...
+            datevec( ...
+            datetime([ ...
+            fliplr(current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_date), ...
+            current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_time 0 ...
+            ]) ...
+            + seconds(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time) ...
+            );
+        software_time = software_datevec(4:6);
+        % get internal clock (RTC)
+        rtc_time = current_float{o_profile_index}.PROFILE_DATA( ...
+            current_float{o_profile_index}.PROFILE_DATA(:, 7) == min(current_float{o_profile_index}.PROFILE_DATA(:, 7)), ...
+            2:4);
+        % if there are multiple pressure levels get the one with the latest
+        % time
+        rtc_time = sortrows(rtc_time, [1:3]);
+        
+        if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty
+           current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = NaN(1, 6);
+           current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = 'none';
+           continue;
+        end
+        
+        rtc_time = rtc_time(1, :);
+        
+        current_datevec = datevec(datetime([software_datevec(1:3), rtc_time]));
+        
+        if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3))
+            % RTC is more trustworthy, so reduce the software date by 1
+            current_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1));
+        elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23))
+            % same here, but now add 1 day
+            current_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1));
+        end
+        
+        current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = current_datevec;
+        current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = 'internal_rtc';
+%         current_datevec = [];
+%         if o_profile_index ~= 1
+%             previous_profile = current_float{o_profile_index - 1};
+%         else
+%             previous_profile = {};
+%         end
+%         
+%         if o_profile_index < profile_count
+%             next_profile = current_float{o_profile_index + 1};
+%         else
+%             next_profile = {};
+%         end
+%         
+%         % decide which time should be taken
+%         
+%         if nemo.profile.has_gps(current_float{o_profile_index}) ...
+%                 && nemo.profile.has_status(current_float{o_profile_index}, 'DONE')
+%             % get gps datetime
+%             current_datevec = nemo.profile.get_gps(current_float{o_profile_index});
+%             current_datevec = current_datevec(8:13);
+%             current_source = 'gps';
+%         elseif nemo.profile.has_iridium(current_float{o_profile_index}) ...
+%                 && nemo.profile.has_status(current_float{o_profile_index}, 'DONE') ...
+%                 && nemo.profile.has_status(previous_profile, 'DONE') ...
+%                 && (isempty(next_profile) || nemo.profile.verify_iridium(current_float{o_profile_index}, next_profile))
+%             % get iridium datetime
+%             current_datevec = nemo.profile.get_iridium(current_float{o_profile_index});
+%             current_datevec = current_datevec(4:9);
+%             current_source = 'iridium';
+%         else
+%             % get internal clock
+%             current_datevec = ...
+%                 datevec( ...
+%                     datetime([ ...
+%                         %fliplr(current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_date), ...
+%                         %current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_time 0 ...
+%                         fliplr(current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_date), ...
+%                         current_float{o_profile_index}.OVERALL_MISSION_INFORMATION.start_time 0 ...
+%                     ]) ...
+%                     + seconds(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time) ...
+%                 );
+%             current_source = 'internal';
+%         end
+%         % store datetime
+%         if (~isfield(current_float{o_profile_index}, 'SECOND_ORDER_INFORMATION'))
+%             current_float{o_profile_index}.SECOND_ORDER_INFORMATION = {};
+%         end
+%         current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec = current_datevec;
+%         current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec_src = current_source;
+    end
+    nemo_data{o_float} = current_float;
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m
new file mode 100644
index 0000000000000000000000000000000000000000..40fb8ded054b1c40d563e107ae5d0083c6e099cc
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/create_best_guess.m
@@ -0,0 +1,53 @@
+function [ nemo_data ] = create_best_guess( p_nemo_data, p_estimation_method )
+%CREATE_BEST_GUESS_POSITION Creates a struct "best_guess_position" in every profile.
+%   Calls the following functions to estimate the best position that is
+%   available from the given profile data:
+%   1) nemo.estimate_best_guess_position
+%   2) nemo.estimate_unknown_positions
+%   WARNING: Please take care that this is the only possible calling procedure if
+%   you want to have consistent position data!
+%   
+%   Returns: p_nemo_data with an additional struct containing the best
+%           position that could be estimated.
+%
+%   Requirements:
+%   - nemo library (this function is part of the library, so you should have it)
+%   - Mapping Toolbox
+%
+
+%% Initialize return variables
+nemo_data = false;
+
+%% Parameter check
+parameter_error = false; % indicates if a parameter error was raised
+
+if (ischar(p_nemo_data)) % read profiles using read_profiles.m
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % read profiles
+        nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    else
+        nemo_data = p_nemo_data; % store nemo data in return variable
+    end
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by nemo.load.data!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% create struct and fill with data
+
+% create best_guess_position struct
+nemo_data = nemo.postprocessing.position.estimate_best_guess(p_nemo_data);
+% estimate all positions that have no gps coordinate
+nemo_data = nemo.postprocessing.position.estimate_unknowns(nemo_data, p_estimation_method);
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m
new file mode 100644
index 0000000000000000000000000000000000000000..76e347a83b2c1a7d93d507b39770b93461814fac
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_best_guess.m
@@ -0,0 +1,142 @@
+function [ estimated_nemo_data ] = estimate_best_guess( p_nemo_data )
+%ESTIMATE_BEST_GUESS_POSITION Gets the most trusted position and stores it into SECOND_ORDER_INFORMATION struct.
+%   WARNING: Do not use this function alone! If you want to have consistent
+%   position data, please use
+%   nemo.postprocessing.create_best_guess_position().
+%
+%   Creates a struct called best_guess_position in every profile and copies
+%   either gps or iridium coordinates to it. If the status of the profile
+%   is "ICE DETECTION", NaN will be placed into the member of the struct.
+%   Same is being done, when there is neither gps nor iridium available.
+%
+%   Returns: Input struct, extended by best_guess_position member in SECOND_ORDER_INFORMATION.
+%
+%   Parameters:
+%   p_nemo_data:    Either has to be a multidimensional struct (created by
+%                   nemo.load.data()) or a string, where the nemo float data
+%                   is stored.
+
+%% Initialize return variables
+estimated_nemo_data = false;
+
+%% Parameter check
+parameter_error = false; % default value if function fails
+
+if (ischar(p_nemo_data)) % load profile data from file
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % load data
+        estimated_nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    else
+        estimated_nemo_data = p_nemo_data; % store nemo data in return variable
+    end
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by nemo.load.data.m!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Estimate best position
+% Priority:
+% 1) GPS
+% 2) Iridium
+
+% setup format structure
+section_name = 'SECOND_ORDER_INFORMATION';
+gps_section_name = 'SURFACE_GPS_DATA';
+gps_section_format_name = 'SURFACE_GPS_DATA_FORMAT';
+iridium_section_name = 'IRIDIUM_DATA';
+iridium_section_format_name = 'IRIDIUM_DATA_FORMAT';
+
+% process every single float
+for o_float_index = 1:length(estimated_nemo_data)
+    % get profile count of current float
+    profile_count = length(estimated_nemo_data{o_float_index});
+    
+    % run through every profile
+    for o_profile_index = 1:profile_count
+        % get current profile
+        current_profile = ...
+            estimated_nemo_data{o_float_index}{o_profile_index};
+        if o_profile_index ~= 1
+            % get previous profile
+            previous_profile = ...
+                estimated_nemo_data{o_float_index}{o_profile_index - 1};
+        else
+            previous_profile = {};
+        end
+        
+        if o_profile_index < profile_count
+            % get previous profile
+            next_profile = ...
+                estimated_nemo_data{o_float_index}{o_profile_index + 1};
+        else
+            next_profile = {};
+        end
+        
+        % check status first
+        if (nemo.profile.has_status(current_profile, 'ICE DETECTION') ...
+                || (~isempty(previous_profile) && nemo.profile.has_status(previous_profile, 'ICE DETECTION')))
+            % no position is available
+            current_profile.(section_name).best_guess_position_lat = NaN;
+            current_profile.(section_name).best_guess_position_lon = NaN;
+            current_profile.(section_name).best_guess_position_src = NaN;
+        elseif (nemo.profile.has_gps(current_profile))
+            % gps available so take it
+            % get indices of lat and lon
+            index_lat = find(not(cellfun('isempty', ...
+                strfind(current_profile.(gps_section_format_name), 'lat') ...
+                )));
+            index_lon = find(not(cellfun('isempty', ...
+                strfind(current_profile.(gps_section_format_name), 'lon') ...
+                )));
+            current_profile.(section_name).best_guess_position_lat = ...
+                current_profile.(gps_section_name)(index_lat);
+            current_profile.(section_name).best_guess_position_lon = ...
+                current_profile.(gps_section_name)(index_lon);
+            current_profile.(section_name).best_guess_position_src = ...
+                'gps';
+            
+            clear index_lon index_lat;
+        elseif (nemo.profile.has_no_gps_but_iridium(current_profile) ...
+                && (isempty(next_profile) || nemo.profile.verify_iridium(current_profile, next_profile)) ...
+                )
+            % iridium available
+            % get indices of lat and lon
+            index_lat = find(not(cellfun('isempty', ...
+                strfind(current_profile.(iridium_section_format_name), 'mean_lat') ...
+                )));
+            index_lon = find(not(cellfun('isempty', ...
+                strfind(current_profile.(iridium_section_format_name), 'mean_lon') ...
+                )));
+            current_profile.(section_name).best_guess_position_lat = ...
+                current_profile.(iridium_section_name)(index_lat);
+            current_profile.(section_name).best_guess_position_lon = ...
+                current_profile.(iridium_section_name)(index_lon);
+            current_profile.(section_name).best_guess_position_src = ...
+                'iridium';
+            clear index_lon index_lat;
+        else
+            current_profile.(section_name).best_guess_position_lat = NaN;
+            current_profile.(section_name).best_guess_position_lon = NaN;
+            current_profile.(section_name).best_guess_position_src = NaN;
+        end
+        
+        % store updated profile
+        estimated_nemo_data{o_float_index}{o_profile_index} = current_profile;
+        
+    end
+    
+    
+end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m
new file mode 100644
index 0000000000000000000000000000000000000000..aee1e44c043516ce0533df18e2786929e46f47f2
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/+position/estimate_unknowns.m
@@ -0,0 +1,185 @@
+function [ fixed_nemo_data ] = estimate_unknowns( p_nemo_data, p_estimation_method )
+%ESTIMATE_UNKNOWN_POSITIONS Calculates the "best guess" profile positions.
+%   WARNING: Do not use this function alone! If you want to have consistent
+%   position data, please use
+%   nemo.postprocessing.position.create_best_guess.
+%
+%   This function iterates over all nemo floats, given in the parameter
+%   p_nemo_data, determining the missing GPS information of profiles where
+%   the float did not come to the water surface. This is done via gcwaypts 
+%   (Mapping Toolbox) between the known GPS/Iridium positions. They have been
+%   stored in the SECOND_ORDER_INFORMATION struct by
+%   nemo.postprocessing.position.estimate_best_guess().
+%   If p_nemo_data is a string, it reads the profiles with the
+%   read_profiles function. So be sure that you added read_profiles.m to
+%   your search path!
+%
+%   Returns: p_nemo_data with interpolated positions, false on fail.
+%
+%   Parameters:
+%   p_nemo_data:    Either has to be a multidimensional struct (created by
+%                   nemo.load.data()) or a string, where the nemo float data
+%                   is stored.
+
+
+%% Initialize return variables
+fixed_nemo_data = false; % default value if function fails
+
+%% Check passed parameter and read profiles if necessary
+parameter_error = false; % indicates if a parameter error was raised
+
+if (ischar(p_nemo_data)) % read profiles using read_profiles.m
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % read profiles
+        fixed_nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    else
+        fixed_nemo_data = p_nemo_data; % store nemo data in return variable
+    end
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by read_profiles.m!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+% If estimation method nan is chosen, do nothing because there is already
+% NaN given in the unknown positions by
+% nemo.postprocessing.position.estimate_best_guess().
+if (strcmp(p_estimation_method, 'nan'))
+    return
+end
+
+%% Position estimation
+
+for o_float_index = 1:length(fixed_nemo_data)
+    
+    current_float = fixed_nemo_data{o_float_index}; % stores current float in tmp var
+    profile_count = length(current_float);
+    
+    
+    % find all pairs that have incomplete positions in between
+    
+    o_profile_index = 1;
+    
+    while (o_profile_index <= profile_count) % process every profile
+        if (~isfield(current_float{o_profile_index}, 'SECOND_ORDER_INFORMATION') ...
+                || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_lat') ...
+                || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_lon') ...
+                || ~isfield(current_float{o_profile_index}.SECOND_ORDER_INFORMATION, 'best_guess_position_src')) % security check
+            disp([mfilename ': No best guess position available in float ' num2str(current_float{o_profile_index}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                ' profile ' num2str(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_profile_number) '! Skipping...']);
+            o_profile_index = o_profile_index + 1;
+            continue
+        end
+        
+        % check for NaN, if it is the first index, the deployment position
+        % will be the first position for the interpolation
+        index_last_position = o_profile_index;
+        while (o_profile_index <= profile_count) ...
+                && isnan(current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_position_lat)
+            o_profile_index = o_profile_index + 1;
+        end
+        index_next_position = o_profile_index;
+        
+        if (index_last_position == index_next_position) % no gap has been found
+            % increase iteration var and continue
+            o_profile_index = o_profile_index + 1;
+            continue;
+        else
+            % a gap has been found, so the last position index is wrong!
+            index_last_position = index_last_position - 1;
+        end
+        
+        % a gap has been found
+        if (index_last_position == 0) % use deployment position as last position
+            last_position.lat = current_float{1}.DEPLOYMENT_INFO.deployment_position(1);
+            last_position.lon = current_float{1}.DEPLOYMENT_INFO.deployment_position(2);
+            last_position.src = 'deployment';
+            last_position.datetime = 0;
+        else
+            last_position.lat = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_lat;
+            last_position.lon = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_lon;
+            last_position.src = current_float{index_last_position}.SECOND_ORDER_INFORMATION.best_guess_position_src;
+            last_position.datetime = current_float{index_last_position}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time;
+        end
+        
+        % if there is no position at all of the float, do not create best
+        % guess for it
+        if index_next_position > profile_count
+            break;
+        end
+        next_position.lat = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_lat;
+        next_position.lon = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_lon;
+        next_position.src = current_float{index_next_position}.SECOND_ORDER_INFORMATION.best_guess_position_src;
+        next_position.datetime = current_float{index_next_position}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time;
+        
+        intermediate_profile_count = index_next_position - index_last_position;
+        
+        switch p_estimation_method
+            case 'position based'
+                position_based_estimation
+            case 'time based'
+                time_based_estimation
+            otherwise
+                warning([mfilename ': Estimation method not available. Using position based instead...']);
+                position_based_estimation
+        end
+        
+        
+        
+    end
+    
+    % store the corrected profiles into struct that will be returned
+    fixed_nemo_data{o_float_index} = current_float;
+    
+    
+end
+
+    function position_based_estimation
+        % calculate intermediate points
+        [intermediate_points_lat, intermediate_points_lon] = gcwaypts( ...
+            last_position.lat, ...
+            last_position.lon, ...
+            next_position.lat, ...
+            next_position.lon, ...
+            intermediate_profile_count ... % function requires legs, not points
+            );
+        
+        % store all positions in best guess variable
+        for i = 1:intermediate_profile_count
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lat = intermediate_points_lat(i + 1);
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lon = intermediate_points_lon(i + 1);
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_src = [ last_position.src '_gcwaypts_' next_position.src ];
+        end
+    end
+
+    function time_based_estimation
+        difference_in_seconds = next_position.datetime - last_position.datetime;
+        
+        [intermediate_points_lat, intermediate_points_lon] = gcwaypts( ...
+            last_position.lat, ...
+            last_position.lon, ...
+            next_position.lat, ...
+            next_position.lon, ...
+            difference_in_seconds + 1 ... % function requires legs, not points
+            );
+        
+        for i = 1:intermediate_profile_count-1
+            profile_datetime = current_float{index_last_position + i}.PROFILE_TECHNICAL_DATA.xmit_surface_start_time;
+            profile_to_last_position = profile_datetime - last_position.datetime;
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lat = intermediate_points_lat(profile_to_last_position);
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_lon = intermediate_points_lon(profile_to_last_position);
+            current_float{index_last_position + i}.SECOND_ORDER_INFORMATION.best_guess_position_src = [ last_position.src '_gcwaypts_' next_position.src ];
+        end
+        
+    end
+
+end % estimate_unknowns
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m
new file mode 100644
index 0000000000000000000000000000000000000000..2b772cd59e390b98a734bda2b0a316104cdbf9e5
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/delete_profile.m
@@ -0,0 +1,68 @@
+function [ filtered_nemo_data ] = delete_profile( p_nemo_data, p_float_serial, p_profile_number )
+%DELETE_PROFILE Deletes the given profile from the given float.
+%   
+%   Parameters:
+%   p_nemo_data         Nemo data, loaded by nemo.load.data().
+%   p_float_number      Input float serial number.
+%   p_profile_number    Profile to be deleted.
+%
+%   Returns:
+%   filtered_nemo_data	Nemo data with deleted profile.
+%
+%
+% TODO: SECURITY CHECKS MISSING
+
+% search float
+float_count = length(p_nemo_data);
+current_float = 1;
+while current_float <= float_count
+    current_serial = ...
+        p_nemo_data{current_float}{1}.FLOAT_IDENTIFICATION.float_serial_number;
+    if current_serial == p_float_serial
+        break
+    else
+        current_float = current_float + 1;
+    end
+end
+
+if current_float > float_count
+    filtered_nemo_data = p_nemo_data;
+    disp([mfilename ': Float could not be found in data, skipping...']);
+    return
+end
+
+% found the right float, now find the profile
+profile_count = length(p_nemo_data{current_float});
+current_profile = 1;
+while current_profile <= profile_count
+    current_profile_number = ...
+        p_nemo_data{current_float}{current_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number;
+    if current_profile_number == p_profile_number
+        break
+    else
+        current_profile = current_profile + 1;
+    end
+end
+
+if current_profile > profile_count
+    filtered_nemo_data = p_nemo_data;
+    disp([mfilename ': Profile could not be found in data, skipping...']);
+    return
+end
+
+filtered_nemo_data = p_nemo_data;
+if current_profile == profile_count
+    filtered_float = p_nemo_data{current_float}(1:end-1);
+elseif current_profile == 1
+    filtered_float = p_nemo_data{current_float}(2:end);
+else
+    filtered_float = {};
+    filtered_float{1:current_profile-1} = ...
+        p_nemo_data{current_float}(1:current_profile-1);
+    filtered_float{current_profile+1:end} = ...
+        p_nemo_data{current_float}(current_profile+1:end);
+end
+filtered_nemo_data{current_float} = filtered_float;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m
new file mode 100644
index 0000000000000000000000000000000000000000..c8ef6e240a95fb4648b520d3e5a1ca05f2d6beed
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/quality_check.m
@@ -0,0 +1,105 @@
+function [ commented_nemo_data ] = quality_check( p_nemo_data )
+%QUALITY_CHECK Does several quality checks.
+%   1) Adds two fields to the SECOND_ORDER_INFORMATION block:
+%       fieldname                           values
+%       xmit_transmittion_type          nrt, delayed, unknown
+%       ice_detected                        0, 1
+%
+%	2) Removes profiles where the best_guess_datevec is before the
+%	   deployment time.
+%
+%   Parameters:
+%   p_nemo_data:            The nemo data.
+%
+%   Returns:
+%   commented_nemo_data:    The nemo data with applied changes.
+
+%% Check passed parameter
+parameter_error = false;
+
+if (~iscell(p_nemo_data))
+    parameter_error = true;
+end
+
+if (parameter_error)
+    disp([mfilename ': Parameter error. Please check your input parameters.']);
+end
+clear parameter_error;
+
+float_count = length(p_nemo_data);
+commented_nemo_data = p_nemo_data;
+
+for o_float = 1:float_count
+    
+    profile_count = length(commented_nemo_data{o_float});
+    current_float = commented_nemo_data{o_float};
+    
+    for o_profile_index = 1:profile_count % process all profiles
+        
+        %% Check if profile has been sent directly after recording
+        
+        if (o_profile_index < profile_count ...
+                && ~nemo.profile.verify_iridium( ...
+                current_float{o_profile_index}, ...
+                current_float{o_profile_index + 1}) ...
+            )
+        
+          switch (nemo.profile.get_status(current_float{o_profile_index}))
+              case 'DONE'
+                  % it has been sent later on, no clue why
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'delayed';
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0;
+              case 'ICE DETECTION'
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'delayed';
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 1;
+              otherwise
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'unknown';
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0;
+          end
+        else
+            current_float{o_profile_index}.SECOND_ORDER_INFORMATION.xmit_transmission_type = 'nrt';
+            switch (nemo.profile.get_status(current_float{o_profile_index}))
+              case 'DONE'
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0;
+              case 'ICE DETECTION'
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 1;
+                otherwise
+                  current_float{o_profile_index}.SECOND_ORDER_INFORMATION.ice_detected = 0;
+          end
+        end
+        
+        %% Check if profile time is before deployment time
+        deploy_datenum = datenum([ ...
+                           fliplr(current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_date), ...
+                           current_float{o_profile_index}.DEPLOYMENT_INFO.deployment_time, ...
+                           0 ...
+                         ]);
+        profile_datenum = datenum(current_float{o_profile_index}.SECOND_ORDER_INFORMATION.best_guess_datevec);
+        if (profile_datenum < deploy_datenum)
+            disp(['Removed profile ' num2str(current_float{o_profile_index}.PROFILE_TECHNICAL_DATA.xmit_profile_number) ...
+                  ' of float ' num2str(current_float{o_profile_index}.FLOAT_IDENTIFICATION.float_serial_number) ...
+                  ' because it was recorded before deployment!']);
+            current_float{o_profile_index} = {};
+        end
+        
+%         if (nemo.profile.positions_differ_by(commented_nemo_data{o_profile_index}, 50))
+%             commented_nemo_data{o_profile_index}.COMMENT.position = 'gps and iridium differ by more than 50km';
+%         end
+
+    end
+    
+    %% Remove all empty profiles
+    current_float = current_float(~cellfun(@(x) isempty(x), current_float));
+    
+    %% Save current float
+    commented_nemo_data{o_float} = current_float;
+end
+
+%% Manual quality checks
+
+
+%% Remove all empty floats
+commented_nemo_data = commented_nemo_data(~cellfun(@(x) isempty(x), commented_nemo_data));
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m
new file mode 100644
index 0000000000000000000000000000000000000000..366b859527123abed2a413ef639de010fccf80d3
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+postprocessing/sort_profile_data.m
@@ -0,0 +1,27 @@
+function [ sorted_nemo_data ] = sort_profile_data( p_nemo_data )
+%SORT_PROFILE_DATA Sorts the CTD profile data of the given floats.
+%   Uses nemo.float.sort_profuile_data function to sort each float
+%   individually.
+%
+%   Parameters:
+%   p_nemo_data:        The nemo data that will be sorted.
+%   
+%   Returns:
+%   sorted_nemo_data:   A copy of p_nemo_data with sorted CTD data.
+
+
+%% Initialize return variable
+sorted_nemo_data = false;
+
+%% Parameter check
+if (~iscell(p_nemo_data))
+    disp([mfilename ': Unknown input type! Please check your input parameter!']);
+    return
+end
+
+%% Sort profile data by time
+sorted_nemo_data = cellfun(@nemo.float.sort_profile_data, p_nemo_data, 'UniformOutput', false);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m b/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m
new file mode 100644
index 0000000000000000000000000000000000000000..de7019e5f196785bcb41354e107753b4b0a70a7a
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/calc_rtc_software_difference.m
@@ -0,0 +1,40 @@
+function [ difference_rtc_software ] = calc_rtc_software_difference( p_profile )
+%CALC_RTC_SOFTWARE_DIFFERENCE Summary of this function goes here
+%   Detailed explanation goes here
+
+software_datevec = ...
+    datevec( ...
+        datetime([ ...
+        fliplr(p_profile.OVERALL_MISSION_INFORMATION.start_date), ...
+        p_profile.OVERALL_MISSION_INFORMATION.start_time 0 ...
+    ]) ...
+    + seconds(p_profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ...
+);
+software_time = software_datevec(4:6);
+% get internal clock (RTC) @ 2000m
+rtc_time = p_profile.PROFILE_DATA( ...
+    p_profile.PROFILE_DATA(:, 7) == max(p_profile.PROFILE_DATA(:, 7)), ...
+    2:4);
+% if there are multiple pressure levels get the one with the
+% earliest time
+rtc_time = sortrows(rtc_time, [1:3]);
+if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty
+    difference_rtc_software = NaN;
+    return;
+end
+rtc_time = rtc_time(end, :);
+
+rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]));
+
+if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3))
+    % RTC is more trustworthy, so reduce the software date by 1
+    rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1));
+elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23))
+    % same here, but now add 1 day
+    rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1));
+end
+
+difference_rtc_software = etime(rtc_datevec, software_datevec) + 10;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m b/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m
new file mode 100644
index 0000000000000000000000000000000000000000..0c2872cda008451746e5c010d316cd9f3659a735
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/compare.m
@@ -0,0 +1,79 @@
+function [ boolean ] = compare( p_profile_a, p_profile_b )
+%COMPARE Checks if the input profiles are equal.
+%   Both input profiles are compared to each other. If one of the
+%   underlying struct/cell/double values are not equal, it will return false.
+%
+%   Parameters:
+%   p_profile_a, p_profile_b: Profiles to be compared. Ideally they are
+%                             previously loaded by nemo.profile.load()
+%
+%   Returns:
+%   boolean:            True if both profiles are equal.
+
+%% Initialize return variables
+boolean = true;
+%% TODO: Parameter check
+
+%% Comparing profiles
+
+compare_variables(p_profile_a, p_profile_b);
+
+    function compare_variables(p_a, p_b)
+        
+        switch class(p_a)
+            case {'double', 'logical'}
+                if (all(all(isnan(p_a))) && all(all(isnan(p_b))))
+                    return;
+                elseif (all(all(isnan(p_a))) && ~all(all(isnan(p_b))))
+                    boolean = false;
+                    return;
+                end
+                if (~strcmp(num2str(p_a), num2str(p_b)))
+                    boolean = false;
+                    return;
+                end
+            case 'char'
+                if (~strcmp(p_a, p_b))
+                    boolean = false;
+                    return;
+                end
+            case 'struct'
+                fieldnames_a = fieldnames(p_a);
+                fieldnames_b = fieldnames(p_b);
+                if (~isequaln(fieldnames_a, fieldnames_b))
+                    boolean = false;
+                    return;
+                end
+                for i = 1:length(fieldnames_a)
+                    compare_variables(p_a.(fieldnames_a{i}), p_b.(fieldnames_b{i}));
+                end
+                
+            case 'cell'
+                [la, ca] = size(p_a);
+                [lb, cb] = size(p_b);
+                if ((la ~= lb) || (ca ~= cb))
+                    boolean = false;
+                    return;
+                end
+                if ((ca > 1) && (la > 1))
+                    for i = 1:la
+                        for k = 1:ca
+                            compare_variables(p_a{i}{k}, p_b{i}{k});
+                        end
+                    end
+                else
+                    la = size(p_a);
+                    for i = 1:la
+                        compare_variables(p_a{i}, p_b{i});
+                    end
+                end
+        end
+        
+        
+        
+        
+        
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/find.m b/lib/vendor/nemo2profilelib/+nemo/+profile/find.m
new file mode 100644
index 0000000000000000000000000000000000000000..8e3f442665f42da0ff6f8087520e76a376e2f2a3
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/find.m
@@ -0,0 +1,19 @@
+function [ profile, index ] = find( p_float, p_profile_number )
+%FIND Searches the profile with p_profile_number in the given float.
+%   Returns the profile and its index in the p_float structure.
+%   If not found, profile contains false.
+
+index = cellfun(@(x) x.PROFILE_TECHNICAL_DATA.xmit_profile_number == p_profile_number, p_float);
+if (~any(index))
+    profile = false;
+    return
+elseif (sum(index) > 1)
+    disp(['Multiple profiles found: ' index]);
+    profile = false;
+    return
+end
+
+profile = p_float{index};
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m
new file mode 100644
index 0000000000000000000000000000000000000000..331c5897757121bae4abde280a6c157d57693218
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_gps.m
@@ -0,0 +1,32 @@
+function [ gps_data ] = get_gps( p_profile )
+%GET_GPS Returns gps data block of the given profile.
+%
+%   Returns: Double of size 1x15 if gps coordinates are available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile where gps data is being extracted.
+
+%% Initializing return variables
+gps_data = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+    return
+end
+
+%% Check if GPS section is available
+if (~isfield(p_profile, 'SURFACE_GPS_DATA'))
+    gps_data = false;
+    return
+else
+    % store all values
+    gps_data = p_profile.SURFACE_GPS_DATA;
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m
new file mode 100644
index 0000000000000000000000000000000000000000..4a2fea06ae8fbb952521fdf862b272200f5a7739
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_iridium.m
@@ -0,0 +1,32 @@
+function [ iridium_mean_data ] = get_iridium( p_profile )
+%GET_IRIDIUM Returns iridium data block of the given profile.
+%
+%   Returns: Double of size 1x9 if iridium information is available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile where gps data is being extracted.
+
+%% Initializing return variables
+iridium_mean_data = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+    return
+end
+
+%% Check if iridium section is available
+if (~isfield(p_profile, 'IRIDIUM_DATA'))
+    iridium_mean_data = false;
+    return
+else
+    % store all values
+    iridium_mean_data = p_profile.IRIDIUM_DATA;
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m
new file mode 100644
index 0000000000000000000000000000000000000000..2546ed0290d4e3270f403852251c9d6fb561e503
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_rtc_datevec_of_ctd_sample.m
@@ -0,0 +1,84 @@
+function [ sample_datevec, rtc_datevecs ] = get_rtc_datevec_of_ctd_sample( p_profile, p_sample_index )
+%GET_TIMESTAMP_OF_CTD_SAMPLE Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Parameter check
+if ~exist('p_sample_index', 'var')
+    p_sample_index = 1;
+end
+
+if ~isstruct(p_profile) || ~isa(p_sample_index, 'double')
+    disp([mfilename ': Please check your input parameter!']);
+    return;
+end
+
+%% Initialize return variable
+sample_datevec = NaN(1, 6);
+
+%% Find CTD sample
+
+ctd_profile = [];
+
+if ~isempty(p_profile.PROFILE_DATA)
+    p = nemo.profile.sort_profile_data(p_profile);
+    ctd_profile = p.PROFILE_DATA;
+    clear p
+else
+    return
+end
+
+software_datevec = ...
+    datevec( ...
+        datetime([ ...
+            fliplr(p_profile.OVERALL_MISSION_INFORMATION.start_date), ...
+            p_profile.OVERALL_MISSION_INFORMATION.start_time 0 ...
+        ]) ...
+        + seconds(p_profile.PROFILE_TECHNICAL_DATA.xmit_ascent_start_time) ...
+);
+software_time = software_datevec(4:6);
+% get internal clock (RTC) @ 2000m
+rtc_time = p_profile.PROFILE_DATA( ...
+    p_profile.PROFILE_DATA(:, 7) == max(p_profile.PROFILE_DATA(:, 7)), ...
+    2:4);
+% if there are multiple pressure levels get the one with the
+% earliest time
+rtc_time = sortrows(rtc_time, [1:3]);
+if isempty(rtc_time) || isempty(software_datevec) % prevent errors if empty
+   return;
+end
+rtc_time = rtc_time(end, :);
+
+rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]));
+
+if (any(rtc_time(1) == 21:23) && any(software_time(1) == 0:3))
+    % RTC is more trustworthy, so reduce the software date by 1
+    rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) - days(1));
+elseif (any(rtc_time(1) == 0:3) && any(software_time(1) == 21:23))
+    % same here, but now add 1 day
+    rtc_datevec = datevec(datetime([software_datevec(1:3), rtc_time]) + days(1));
+end
+        
+        
+        
+% get ctd, rtc time
+rtc_times = p_profile.PROFILE_DATA(:, 2:4);
+% add pseudo date to profile samples
+pseudo_datenums = datenum([ ...
+    zeros(size(ctd_profile, 1), 2), ...
+    flipud((1:size(ctd_profile, 1))'), ...
+    rtc_times ...
+]);
+
+diff_in_seconds = mod(flipud(diff(flipud(pseudo_datenums))), 1) * 86400;
+
+rtc_datevecs = [rtc_datevec];
+for i = 1:length(diff_in_seconds)
+    rtc_datevecs = [ rtc_datevecs; datevec(datetime(rtc_datevecs(i, :)) + seconds(diff_in_seconds(end - i + 1)))];
+end
+rtc_datevecs = flipud(rtc_datevecs);
+
+sample_datevec = rtc_datevecs(p_sample_index);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m b/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m
new file mode 100644
index 0000000000000000000000000000000000000000..b4fc6590eda09afafdd54551044c02e4c93e6aa9
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/get_status.m
@@ -0,0 +1,13 @@
+function [ status ] = get_status( p_profile )
+%GET_STATUS Returns the xmit_upcast_status value of the given profile.
+%
+%   Returns: String, containing the profiles xmit_upcast_status.
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+status = strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m
new file mode 100644
index 0000000000000000000000000000000000000000..fbf8b41f3633eace9bc580ef00bf27fa07ff8bea
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_best_guess.m
@@ -0,0 +1,36 @@
+function [ boolean ] = has_best_guess( p_profile )
+%HAS_BEST_GUESS Checks if profile has best guess coordinates as well as time.
+%
+%   Returns: True if best guess information is available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check if GPS section is available
+if (~isfield(p_profile, 'SECOND_ORDER_INFORMATION') ...
+        || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_lat') ...
+        || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_lon') ...
+        || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_position_src'))
+        || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_datevec'))
+        || ~isfield(p_profile.SECOND_ORDER_INFORMATION, 'best_guess_datevec_src'))
+    boolean = false;
+    return
+else
+    % check if the values are available
+    boolean = ~isnan(p_profile.SECOND_ORDER_INFORMATION.best_guess_position_lat);
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m
new file mode 100644
index 0000000000000000000000000000000000000000..1e7a70601d3442a391830b36d08f41bf5e717f69
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_gps.m
@@ -0,0 +1,31 @@
+function [ boolean ] = has_gps( p_profile )
+%HAS_GPS Checks if profile has gps coordinates.
+%
+%   Returns: True if gps coordinates are available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check if GPS section is available
+if (~isfield(p_profile, 'SURFACE_GPS_DATA'))
+    boolean = false;
+    return
+else
+    % check if the values are available
+    boolean = (~any(isnan(p_profile.SURFACE_GPS_DATA)) && ~isempty(p_profile.SURFACE_GPS_DATA));
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m
new file mode 100644
index 0000000000000000000000000000000000000000..707128b1b36409a1ae7dc5b6eb6254e2c1ddd87e
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_iridium.m
@@ -0,0 +1,31 @@
+function [ boolean ] = has_iridium( p_profile )
+%HAS_IRIDIUM Checks if profile has iridium coordinates.
+%
+%   Returns: True if iridium coordinates are available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check coordinates
+if (~isfield(p_profile, 'IRIDIUM_DATA'))
+    boolean = false;
+    return
+else
+    % check if the values are available
+    boolean = (~any(isnan(p_profile.IRIDIUM_DATA)));
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m
new file mode 100644
index 0000000000000000000000000000000000000000..7f4c56b44a932dabcd741f9c34e8aba20a63ec8c
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_gps_but_iridium.m
@@ -0,0 +1,52 @@
+function [ boolean ] = has_no_gps_but_iridium( p_profile )
+%HAS_NO_GPS_BUT_IRIDIUM Checks if profile has iridium coordinates, but no gps.
+%
+%   Returns: True if iridium but no gps coordinates are available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check if gps section and values are available
+if (isfield(p_profile, 'SURFACE_GPS_DATA'))
+    % check if the values are available
+    gps_available = (~any(isnan(p_profile.SURFACE_GPS_DATA))) && ~isempty(p_profile.SURFACE_GPS_DATA);
+else
+    gps_available = false;
+end
+
+%% Check if iridium is available
+if (isfield(p_profile, 'IRIDIUM_DATA') && ~isempty(p_profile.IRIDIUM_DATA))
+    % check if the values are available
+    % get indices of lat and lon
+    index_lat = find(not(cellfun('isempty', ...
+        strfind(p_profile.('IRIDIUM_DATA_FORMAT'), 'mean_lat') ...
+        )));
+    index_lon = find(not(cellfun('isempty', ...
+        strfind(p_profile.('IRIDIUM_DATA_FORMAT'), 'mean_lon') ...
+        )));
+    boolean = (~isnan(p_profile.('IRIDIUM_DATA')(index_lat)) && ~isnan(p_profile.('IRIDIUM_DATA')(index_lon)));
+    
+    if (boolean && ~gps_available)
+        boolean = true;
+        return
+    else
+        boolean = false;
+        return;
+    end
+else
+    boolean = false;
+    return;
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m
new file mode 100644
index 0000000000000000000000000000000000000000..d2debcda753b87d901a37f21e3db6fa4ef88b227
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_no_iridium_but_gps.m
@@ -0,0 +1,31 @@
+function [ boolean ] = has_no_iridium_but_gps( p_profile )
+%HAS_NO_IRIDIUM_BUT_GPS Checks if profile has gps coordinates, but no iridium.
+%
+%   Returns: True if gps but no iridium coordinates are available.
+%            Displays a warning if parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check coordinates
+
+boolean = ( ...
+        isnan(p_profile.iridium_mean_lat) ...
+        && isnan(p_profile.iridium_mean_lon) ...
+        && ~isnan(p_profile.gps_lat) ...
+        && ~isnan(p_profile.gps_lon) ...
+        );
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m
new file mode 100644
index 0000000000000000000000000000000000000000..16800bfe02cca2b83ec2bfa3bf290c3d5091e671
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status.m
@@ -0,0 +1,36 @@
+function [ boolean ] = has_status( p_profile, p_status )
+%HAS_STATUS Returns true if given profile has given status, false otherwise.
+%   Compares xmit_upcast_status of the given profile with the p_status
+%   parameter.
+%
+%   Returns: True or false.
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+%   p_status:           Expected xmit_upcast_status. 
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if isempty(p_profile)
+    return; % because no values given
+end
+
+if (~isstruct(p_profile) ...
+        || ~ischar(p_status))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check status
+if isnan(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status)
+    boolean = false;
+else
+    boolean = (strcmp(p_status, strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status)));
+end
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m
new file mode 100644
index 0000000000000000000000000000000000000000..3db8a961eb48b157347d32d02cfd845fbe1efa26
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_coordinates.m
@@ -0,0 +1,56 @@
+function [ boolean ] = has_status_and_coordinates( p_profile, p_status, p_max_coord_dist )
+%HAS_STATUS_AND_COORDINATES DEPRECATED! Not required anymore! Checks if profile has valid coordinates and given status.
+%   Checks if the given profile has the given status as well as valid iridium and
+%   gps coordinates. If the distance between iridium and gps exceeds
+%   p_max_coord_dist, false is being returned. The distance is being
+%   calculated via build in MATLAB distance function.
+%
+%   Returns: True if all conditions are met. Displays a warning if
+%   parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+%   p_status:           Expected xmit_upcast_status.
+%   p_max_coord_dist:   Maximum distance in km between iridium and gps.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile) ...
+        || ~ischar(p_status) ...
+        || ~isnumeric(p_max_coord_dist))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check status, skip if '' is given
+if (~strcmp(p_status, strtrim(p_profile.PROFILE_TECHNICAL_DATA.xmit_upcast_status)) ...
+        && ~strcmp(p_status, ''))
+    return;
+end
+
+%% Check coordinates
+
+if (~nemo.profile.has_gps(p_profile) || ~nemo.profile.has_iridium(p_profile))
+    return;
+end
+
+% get distance in between
+difference = deg2km( ...
+    distance('gc', ... % convert to km for easier handling
+        [p_profile.SURFACE_GPS_DATA(14), p_profile.SURFACE_GPS_DATA(15)], ...
+        [p_profile.IRIDIUM_DATA(1), p_profile.IRIDIUM_DATA(2)] ...
+    ) ...
+);
+
+if (difference > p_max_coord_dist)
+    return;
+end
+
+boolean = true;
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m
new file mode 100644
index 0000000000000000000000000000000000000000..250b57db87671f98d80c6337b182ee90b3de4fb0
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/has_status_and_gps.m
@@ -0,0 +1,42 @@
+function [ boolean ] = has_status_and_gps( p_profile, p_status )
+%HAS_STATUS_AND_GPS Checks if profile has gps coordinates and given status.
+%   Checks if the given profile has the given status as well as
+%   gps coordinates.
+%
+%   Returns: True if all conditions are met. Displays a warning if
+%   parameters are wrong, but returns false!
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+%   p_status:           Expected xmit_upcast_status.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile) ...
+        || ~ischar(p_status))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check status
+if (~strcmp(p_status, strtrim(p_profile.xmit_upcast_status)))
+    return;
+end
+
+%% Check coordinates
+
+if ( ... % one of them is NaN
+        isnan(p_profile.gps_lat) ...
+        || isnan(p_profile.gps_lon) ...
+        )
+    return;
+end
+
+boolean = true;
+
+return;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/load.m b/lib/vendor/nemo2profilelib/+nemo/+profile/load.m
new file mode 100644
index 0000000000000000000000000000000000000000..004318a77245dfb9c8cad803753439c5239b7e2f
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/load.m
@@ -0,0 +1,165 @@
+function [ profile ] = load( p_filename )
+%LOAD Loads a .profile file into memory.
+%   
+%   Returns: A struct containing all parameter and data that has been read from
+%   the datafile. WARNING: Does not check if something went wrong while reading
+%   the file.
+%   
+%   Parameters:
+%   p_filename:    Absolute filename of the file to read.
+%
+
+%% TODO:
+% - remove sections and make this function recognize automatically which type
+%   of variable should be used, same as in nemo.profile.save().
+
+%% Initialize input parameter
+profile = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~ischar(p_filename))
+    warning([mfilename ': Please check input parameter. p_filename is not a string!']);
+    parameter_error = true;
+else
+    % get filename details
+    [pathstr, name, ext] = fileparts(p_filename);
+    if (~strcmp(ext, '.profile'))
+        warning([mfilename ': Please check input parameter. Wrong file extension! Should be .profile!']);
+        parameter_error = true;
+    end
+end
+
+if parameter_error
+    return; % return if parameter check has not been passed successfully
+else
+    clear parameter_error;
+end
+
+%% Load profile into variable
+
+% initialize all required variables
+profile = {};
+current_section = '';
+comment_char = '%'; % character to indicate comments
+section_char = '['; % character indicating new section
+
+% open file
+file_id = fopen(p_filename);
+fseek(file_id, 0, 'bof');
+
+
+% read file line by line
+current_line = fgetl(file_id);
+while (ischar(current_line))
+    if length(current_line) == 0
+        current_line = fgetl(file_id);
+        continue
+    end
+    
+    switch current_line(1)
+        case section_char % this indicates a new section
+            current_line = strtrim(current_line);
+            current_section = current_line(2:end-1);
+            read_section
+        otherwise
+            current_line = fgetl(file_id);
+    end
+
+end
+
+fclose(file_id);
+
+    function read_section
+        % read complete section
+        current_line = fgetl(file_id);
+        
+        if isempty(current_line)
+            % initialize an empty array to prevent errors when trying to
+            % access in another function
+            profile.(current_section) = [];
+        end
+        
+        while ischar(current_line) && ~isempty(current_line) ...
+                && ~strcmp(current_line(1), section_char)
+            
+            if strcmp(current_line(1), comment_char) % ignore commented lines
+                current_line = fgetl(file_id);
+                continue
+            end
+		
+			% if it contains a comment char anywhere, just take the string
+            % before it
+            found_comment = strfind(current_line, comment_char);
+            if ~isempty(found_comment)
+                current_line = current_line(1:found_comment(1)-1);
+            end
+            
+            switch (current_section)
+                case { ...
+                        'FLOAT_IDENTIFICATION', ...
+                        'OVERALL_MISSION_INFORMATION', ...
+                        'DEPLOYMENT_INFO', ...
+                        'PROFILE_TECHNICAL_DATA', ...
+                        'BOTTOM_VALUES_DURING_DRIFT', ...
+                        'PROFILE_HEADER', ...
+                        'QUALITY_CONTROL_HEADER', ...
+                        'SECOND_ORDER_INFORMATION' ...
+                        }
+                    % get variable name and value
+                    [token, remain] = strtok(current_line);
+                    % try to convert remain to a number
+                    remain_as_num = str2num(strtrim(remain));
+                    if (~isempty(remain_as_num))
+                        profile.(current_section).(token(2:end)) = remain_as_num;
+                    else
+                        profile.(current_section).(token(2:end)) = strtrim(remain);
+                    end
+                case { ...
+                        'RAFOS_VALUES_FORMAT', ...
+                        'PROFILE_DATA_FORMAT', ...
+                        'SURFACE_GPS_DATA_FORMAT', ...
+                        'IRIDIUM_POSITIONS_FORMAT', ...
+                        'IRIDIUM_DATA_FORMAT', ...
+                        'PROFILE_DATA_HEADER' ...
+                        }
+                    % remove -format, split afterwards
+                    [token, remain] = strtok(current_line);
+                    if (~strcmp(token, '-format'))
+                        profile.(current_section) = [ token, ...
+                            strsplit(strtrim(remain), {' ', '\t'}, 'CollapseDelimiters', true)];
+                    else
+                        profile.(current_section) = strsplit( ...
+                            strtrim(remain), {' ', '\t'}, 'CollapseDelimiters', true);
+                    end
+                    %break
+                case { ...
+                        'RAFOS_VALUES', ...
+                        'PROFILE_DATA', ...
+                        'SURFACE_GPS_DATA', ...
+                        'IRIDIUM_POSITIONS', ...
+                        'IRIDIUM_DATA' ...
+                        }
+                    % store into matrix
+                    % read values and append them into the same var as long
+                    % as no empty line occured (convention)
+                    profile.(current_section) = [];
+                    while (ischar(current_line) && ~strcmp(current_line, ''))
+                        profile.(current_section) = [ ...
+                            profile.(current_section); ...
+                            str2double(strsplit(current_line, {'\t', ' '}, 'CollapseDelimiters', true)) ...
+                            ];
+                        current_line = fgetl(file_id);
+                    end
+                    break
+                    
+            end
+            
+            current_line = fgetl(file_id);
+        end
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m b/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m
new file mode 100644
index 0000000000000000000000000000000000000000..26e7317e69f1b47a839868744dd138331106c379
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/map_to.m
@@ -0,0 +1,75 @@
+function [ mapped_profile ] = map_to( p_profile, p_mapping, p_skip_undefined )
+%MAP_TO Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize return variable
+mapped_profile = false;
+
+%% Parameter check
+if ~isstruct(p_profile) || ~isstruct(p_mapping)
+    disp([mfilename ': Please check your input parameter!']);
+    return
+end
+
+%% Map parameters
+mapped_profile = struct;
+
+fnames_profile = fieldnames(p_profile);
+
+for i = 1:length(fnames_profile)
+    
+    % check if mapping exists for this field
+    if ~isfield(p_mapping, fnames_profile{i})
+        if exist('p_skip_undefined', 'var') && ~p_skip_undefined
+            mapped_profile.(fnames_profile{i}) = p_profile.(fnames_profile{i});
+        end
+        continue
+    end
+    
+    current_field = p_profile.(fnames_profile{i});
+    
+    if ~isstruct(current_field) % we only want to modify structs atm
+        continue
+    end
+    
+    mapped_profile.(fnames_profile{i}) = struct;
+    
+    % get the fieldnames in the struct
+    subfnames = fieldnames(current_field);
+    for k = 1:length(subfnames)
+        
+        if ~isfield(p_mapping.(fnames_profile{i}), subfnames{k})
+            if exist('p_skip_undefined', 'var') && ~p_skip_undefined
+                mapped_profile.(fnames_profile{i}).(subfnames{k}) = p_profile.(fnames_profile{i}).(subfnames{k});
+            end
+            continue
+        end
+        
+        % check if the previous value is the same
+        if isfield(mapped_profile.(fnames_profile{i}), (p_mapping.(fnames_profile{i}).(subfnames{k})))
+            % append the value instead of copying
+            mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) = ...
+                [ ...
+                    mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) ...
+                    p_profile.(fnames_profile{i}).(subfnames{k})
+                ];
+            continue
+        end
+        
+        % check if there are multiple values in the field
+        values = strsplit(p_mapping.(fnames_profile{i}).(subfnames{k}), ',');
+        if length(values) > 1
+            for m = 1:length(values)
+                mapped_profile.(fnames_profile{i}).(values{m}) = p_profile.(fnames_profile{i}).(subfnames{k})(m);
+            end
+            continue
+        end
+        
+        mapped_profile.(fnames_profile{i}).(p_mapping.(fnames_profile{i}).(subfnames{k})) = p_profile.(fnames_profile{i}).(subfnames{k});
+        
+    end
+   
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m b/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m
new file mode 100644
index 0000000000000000000000000000000000000000..1409e191efc48efdc94386e274a8f6fbbf1db948
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/positions_differ_by.m
@@ -0,0 +1,38 @@
+function [ boolean ] = positions_differ_by( p_profile, p_distance )
+%POSITIONS_DIFFER_BY Checks if gps and iridium coordinates differ by given distance.
+%
+%   Returns: True if the distance exceeds p_distance [km].
+%
+%   Parameters:
+%   p_profile:          The nemo profile that is being checked.
+%   p_distance:         The maximum distance that is being accepted.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile))
+    disp([mfilename ': Wrong parameter. Please check your input. Returning false!']);
+end
+
+%% Check coordinates
+
+if (~nemo.profile.has_gps(p_profile) || ~nemo.profile.has_iridium(p_profile))
+    return;
+end
+
+% get distance in between
+difference = deg2km( ...
+    distance('gc', ... % convert to km for easier handling
+        [p_profile.SURFACE_GPS_DATA(14), p_profile.SURFACE_GPS_DATA(15)], ...
+        [p_profile.IRIDIUM_DATA(1), p_profile.IRIDIUM_DATA(2)] ...
+    ) ...
+);
+
+if (difference > p_distance)
+    boolean = true;
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/save.m b/lib/vendor/nemo2profilelib/+nemo/+profile/save.m
new file mode 100644
index 0000000000000000000000000000000000000000..1aeddead6bb9c4323550501711622d589fa28b93
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/save.m
@@ -0,0 +1,179 @@
+function [ boolean ] = save( p_header_filename, p_profile, p_output_file, p_overwrite )
+%SAVE Writes the input profile to the output file. 
+%   This function saves the p_profile structure to an ASCII textfile with
+%   .profile extension. The structure of the parameter is being transferred
+%   equally into the textfile so that it can be loaded by the load
+%   function.
+%   TO BE OPTIMIZED: This function does not recursively checks for the
+%   variable type. Only up to the second level.
+%
+%   Returns: Boolean variable, being false if something went wrong during
+%            write.
+%
+%   Parameters:
+%   p_header_file:  Contains the absolute filename of the header being
+%                   written to the output file. There is no comment
+%                   character being added by this function.
+%   p_profile:      Contains the profile structure that will be written to
+%                   the output file.
+%   p_output_file:  Absolute file path to the output file that will be
+%                   written.
+%   p_overwrite:    If set to true, the output file will be overwritten if
+%                   it exists.
+%   
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~isstruct(p_profile) || ~ischar(p_header_filename) ...
+    || ~ischar(p_output_file))
+    parameter_error = true;
+end
+
+if (~exist('p_overwrite', 'var') || isempty(p_overwrite))
+    p_overwrite = 1; % default setting is to overwrite existing profile files
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% File existence checks
+
+if exist(p_output_file, 'file') == 2 && ~p_overwrite
+    % file will not be overwritten
+    boolean = true;
+    return
+end
+
+header_file_id = 0;
+if exist(p_header_filename, 'file') ~= 2
+    disp([mfilename ': Header file does not exist! Skipping...']);
+    return;
+else % read header file
+    header_file_id = fopen(p_header_filename, 'r');
+    header_content = fscanf(header_file_id, '%c');
+    fclose(header_file_id);
+    clear header_file_id
+end
+
+%% Write file
+
+% prepare for writing
+help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'];
+desired_tab_count = 12;
+section_names = fieldnames(p_profile); % will be stored in between []
+section_count = length(section_names);
+output_file_id = fopen(p_output_file, 'w'); % open file for writing
+double_format = '%.15g';
+
+% write header
+fprintf(output_file_id, '%s', header_content); % write header to file
+fprintf(output_file_id, '\n\n'); % add 2 lines to separate data from header
+
+% write content
+for o_section = 1:section_count % run through every section
+    
+    % write section name to file
+    fprintf(output_file_id, ['[' section_names{o_section} ']' '\n' ]);
+        
+    % check which type of data it is and decide how to output
+    switch class(p_profile.(section_names{o_section}))
+        case 'struct'
+            write_struct(p_profile.(section_names{o_section}));
+            
+        case 'cell'
+            write_cell(p_profile.(section_names{o_section}));
+            
+        case 'double'
+            write_double(p_profile.(section_names{o_section}));
+            
+        otherwise
+    end
+    
+    fprintf(output_file_id, '\n');
+    
+
+end
+
+% close file
+fclose(output_file_id);
+
+boolean = true; % update return variable
+
+
+    function [ tabs ] = get_tabs_to_insert(p_member_name)
+        % this function computes the required tabs to insert, making the
+        % textfile easier to read.
+        tablength = 4;
+        tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength);
+        tabs = help_tabs(1:(tab_count*2));
+    end
+
+    function write_struct(p_input_struct)
+        % get all fieldnames and write them to the file
+        member_names = fieldnames(p_input_struct);
+        member_count = length(member_names);
+        for o_member = 1:member_count % run through all members
+            % calculate how many tabs have to be inserted to have a
+            % good table layout
+            tabstring = get_tabs_to_insert(member_names{o_member});
+            fprintf(output_file_id, ['-' member_names{o_member} tabstring]);
+            current_member = p_input_struct.(member_names{o_member});
+            current_member_class = class(current_member);
+            switch current_member_class
+                case {'double', 'logical'}
+                    write_double(current_member);
+                case 'char'
+                    fprintf(output_file_id, [current_member '\n']);
+                otherwise
+                    disp(['Unknown class in file: ' class(current_member)]);
+            end
+
+        end
+        clear o_member
+    end
+
+    function write_cell(p_input_cell)
+        % run through every cell member and check the class
+        for o_cell_line = 1:size(p_input_cell, 1)
+            for o_cell_column = 1:size(p_input_cell, 2)
+                current_value = p_input_cell{o_cell_line, o_cell_column};
+                switch class(current_value)
+                    case 'char'
+                        fprintf(output_file_id, current_value);
+                    case {'double', 'logical'}
+                        fprintf(output_file_id, double_format, current_value);
+                    otherwise
+                        disp(['Unknown class in file: ' class(current_value)]);
+                end
+                if (o_cell_column ~= size(p_input_cell, 2))
+                    fprintf(output_file_id, '\t');
+                end
+            end
+            
+            fprintf(output_file_id, '\n');
+        end
+    end
+
+    function write_double(p_input_double)
+        
+        for o_double = 1:size(p_input_double, 1)
+            if length(p_input_double(o_double, :)) > 1
+                fprintf(output_file_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1)));
+            end
+            fprintf(output_file_id, [double_format '\n'], p_input_double(o_double, end));
+        end
+        clear o_double
+        
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m b/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m
new file mode 100644
index 0000000000000000000000000000000000000000..b32f7c63af274c2962d898d49234bce37b0284cd
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/sort_profile_data.m
@@ -0,0 +1,27 @@
+function [ sorted_profile ] = sort_profile_data( p_profile )
+%SORT_PROFILE_DATA Sorts the CTD profile data ascending by pressure.
+%   See nemo.float.sort_profile_data for more details.
+%
+%   Parameters:
+%   p_profile:          The profile to be sorted.
+%
+%   Returns:
+%   sorted_profile:     A copy of the profile with sorted CTD data.
+
+
+%% Initialize return variable
+sorted_profile = false;
+
+%% Parameter check
+if (~isstruct(p_profile) || ~isfield(p_profile, 'PROFILE_DATA'))
+    disp([mfilename ': Unknown input type! Please check your input parameter!']);
+    return
+end
+
+%% Sort profile data by time
+sorted_profile = p_profile;
+sorted_profile.PROFILE_DATA = sortrows(p_profile.PROFILE_DATA, 7);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m b/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m
new file mode 100644
index 0000000000000000000000000000000000000000..352fb45f20c3116acbaad3a2f9d8e77fb1fbb2d9
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+profile/verify_iridium.m
@@ -0,0 +1,38 @@
+function [ boolean ] = verify_iridium( p_profile, p_next_profile )
+%VERIFY_IRIDIUM Checks if the Iridium data represents profile time and position.
+%
+%   Returns: True if Iridium time of current profile is smaller than the
+%            one of the next profile.
+%
+%   Parameters:
+%   p_profile:          The current nemo profile.
+%   p_next_profile:     The nemo profile with profile no +1.
+
+%% Initializing return variables
+boolean = false;
+
+%% Parameter check
+
+if (~isstruct(p_profile) || ~isstruct(p_next_profile))
+    disp([mfilename ': Wrong parameters. Please check your input. Returning false!']);
+    return
+end
+
+%% Check if iridium section is available
+if (~isfield(p_profile, 'IRIDIUM_DATA') || ~isfield(p_next_profile, 'IRIDIUM_DATA'))
+    boolean = false;
+    return
+else % compare it to next profile
+    % the current profile datetime has to be lower than the one of the next
+    % profile
+    boolean = ( ...
+        datenum(p_profile.IRIDIUM_DATA(4:9)) ...
+        < ...
+        datenum(p_next_profile.IRIDIUM_DATA(4:9)) ...
+    );
+    
+end
+
+return;
+
+end
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m
new file mode 100644
index 0000000000000000000000000000000000000000..28ed1cdd5fcf6a071235f2df6abfdde345ac21a3
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/create.m
@@ -0,0 +1,239 @@
+function [ rfb, metadata ] = create( p_nemo_float, p_float_metadata )
+%CREATE Creates the rfb structure of the given floats.
+%   
+%   Parameters:
+%   p_nemo_float:           The floats to process.
+%   p_float_metadata:       The floats metadata.
+%
+%   Returns:
+%   rfb:                    The rfb structure.
+%   metadata:               The corresponding metadata. Floats without
+%                           RAFOS have been sorted out.
+
+%% Initialize return variables
+rfb = false;
+
+%% Parameter check
+if (~iscell(p_nemo_float)) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+end
+
+% if it is the first profile, it is a struct, otherwise a cell
+switch (class(p_nemo_float{1}))
+    case 'cell'
+        % sort out all floats without rafos data
+%         sending_floats_metadata = {};
+%         for o = 1:length(p_nemo_float) % run through every float
+%             current_serial = p_nemo_float{o}{1}.FLOAT_IDENTIFICATION.float_serial_number;
+%             for i = 1:length(p_float_metadata)
+%                 if (p_float_metadata{i}.GENERAL_INFO.SERIAL == current_serial)
+%                     sending_floats_metadata{o} = p_float_metadata{i};
+%                 end
+%             end
+%         end
+%         rfb = cellfun(@create_rfb_float, p_nemo_float, sending_floats_metadata', 'UniformOutput', false);
+        rfb = cellfun(@create_rfb_float, p_nemo_float, p_float_metadata, 'UniformOutput', false);
+%         metadata = sending_floats_metadata;
+        metadata = p_float_metadata;
+    case 'struct'
+        rfb = create_rfb_float(p_nemo_float, p_float_metadata);
+        metadata = p_float_metadata;
+    otherwise
+        disp([mfilename ': Type ' class(p_nemo_float{1}) ' not supported!' ]);
+        return
+end
+
+
+    function rfb_float = create_rfb_float(float, metadata)
+        
+        if(~isfield(float{1}, 'SECOND_ORDER_INFORMATION'))
+            disp([ mfilename ': Generation of rfb file requires field SECOND_ORDER_INFORMATION!']);
+            return;
+        end
+
+        rfb_float = struct();
+        
+        %% Create header information
+        FLOAT = struct( ...
+            'floatname', float{1}.FLOAT_IDENTIFICATION.float_serial_number, ...
+            'type', '', ...
+            'projectname', metadata.RAFOS.GENERAL_INFO.projectname, ...
+            'pttdec', '', ...
+            'ptthex', '', ...
+            'pttrep', metadata.RAFOS.GENERAL_INFO.pttrep, ...
+            'launchpos', float{1}.DEPLOYMENT_INFO.deployment_position, ...
+            'launchtime', [ ...
+            fliplr(float{1}.DEPLOYMENT_INFO.deployment_date), ...
+            float{1}.DEPLOYMENT_INFO.deployment_time ...
+            ], ...
+            'recoverpos', [ ...
+            float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lat, ...
+            float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lon ...
+            ], ...
+            'recovertime', float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec, ...
+            'offset', float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec, ...
+            'cycle', [ ...
+            1, ...
+            float{1}.DEPLOYMENT_INFO.deployment_position, ...
+            fliplr(float{1}.DEPLOYMENT_INFO.deployment_date), ...
+            float{1}.DEPLOYMENT_INFO.deployment_time, ...
+            float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lat, ...
+            float{end}.SECOND_ORDER_INFORMATION.best_guess_position_lon, ...
+            float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec ...
+            ], ...
+            'phasespercycle', 0, ... % is being set later, not available up to this point
+            'schedule', metadata.RAFOS.GENERAL_INFO.schedule, ...
+            'phasereftime', metadata.RAFOS.GENERAL_INFO.phasereftime, ...
+            'windowsperphase', metadata.RAFOS.GENERAL_INFO.windowsperphase, ...
+            'toaperwindow', metadata.RAFOS.GENERAL_INFO.toaperwindow, ...
+            'toaperphase', metadata.RAFOS.GENERAL_INFO.toaperphase, ...
+            'correlationrange', metadata.RAFOS.GENERAL_INFO.correlationrange, ...
+            'windowrange', metadata.RAFOS.GENERAL_INFO.windowrange, ...
+            'windowstart', metadata.RAFOS.GENERAL_INFO.windowstart, ...
+            'windowlength', metadata.RAFOS.GENERAL_INFO.windowlength, ...
+            'progdepth', metadata.RAFOS.GENERAL_INFO.progdepth, ...
+            'progtemp', metadata.RAFOS.GENERAL_INFO.progtemp, ...
+            'tempequation', metadata.RAFOS.GENERAL_INFO.tempequation, ...
+            'tempcoeff', metadata.RAFOS.GENERAL_INFO.tempcoeff, ...
+            'presequation', metadata.RAFOS.GENERAL_INFO.presequation, ...
+            'prescoeff', metadata.RAFOS.GENERAL_INFO.prescoeff, ...
+            'signal_length', metadata.RAFOS.GENERAL_INFO.signal_length, ...
+            'comment', metadata.RAFOS.GENERAL_INFO.comment, ...
+            'edited_by', metadata.RAFOS.GENERAL_INFO.edited_by, ...
+            'edited_on', datevec(now), ...
+            'variables', metadata.RAFOS.GENERAL_INFO.variables ...
+            );
+
+        SAT_FORMAT = struct( ...
+            'lat_sat', metadata.RAFOS.GPS_FORMAT.lat, ...
+            'lon_sat', metadata.RAFOS.GPS_FORMAT.lon, ...
+            'src', metadata.RAFOS.GPS_FORMAT.src, ...
+            'year_sat', metadata.RAFOS.GPS_FORMAT.year, ...
+            'month_sat', metadata.RAFOS.GPS_FORMAT.month, ...
+            'day_sat', metadata.RAFOS.GPS_FORMAT.day, ...
+            'hour_sat', metadata.RAFOS.GPS_FORMAT.hour, ...
+            'minutes_sat', metadata.RAFOS.GPS_FORMAT.minutes, ...
+            'seconds_sat', metadata.RAFOS.GPS_FORMAT.seconds, ...
+            'year_rtc', metadata.RAFOS.GPS_FORMAT.year_rtc, ...
+            'month_rtc', metadata.RAFOS.GPS_FORMAT.month_rtc, ...
+            'day_rtc', metadata.RAFOS.GPS_FORMAT.day_rtc, ...
+            'hour_rtc', metadata.RAFOS.GPS_FORMAT.hour_rtc', ...
+            'minutes_rtc', metadata.RAFOS.GPS_FORMAT.minutes_rtc, ...
+            'seconds_rtc', metadata.RAFOS.GPS_FORMAT.seconds_rtc ...
+            );
+        
+        VARIABLE_LIST = struct( ...
+            'line_number', metadata.RAFOS.VARIABLE_LIST.line_number, ...
+            'start_listen_yr', metadata.RAFOS.VARIABLE_LIST.start_listen_yr, ...
+            'start_listen_mon', metadata.RAFOS.VARIABLE_LIST.start_listen_mon, ...
+            'start_listen_day', metadata.RAFOS.VARIABLE_LIST.start_listen_day, ...
+            'start_listen_hr', metadata.RAFOS.VARIABLE_LIST.start_listen_hr, ...
+            'start_listen_min', metadata.RAFOS.VARIABLE_LIST.start_listen_min, ...
+            'start_listen_sec', metadata.RAFOS.VARIABLE_LIST.start_listen_sec, ...
+            'time_of_arrival', metadata.RAFOS.VARIABLE_LIST.time_of_arrival, ...
+            'correlation_height', metadata.RAFOS.VARIABLE_LIST.correlation_height, ...
+            'pressure', metadata.RAFOS.VARIABLE_LIST.pressure, ...
+            'temperature', metadata.RAFOS.VARIABLE_LIST.temperature, ...
+            'pres_counts', metadata.RAFOS.VARIABLE_LIST.pres_counts, ...
+            'temp_counts', metadata.RAFOS.VARIABLE_LIST.temp_counts ...
+            );
+        
+        
+        %% Collect all data information
+        
+        SAT_DATA = nemo.float.collect(float, 'SURFACE_GPS_DATA', '', 'combine');
+        
+        rafos_data = nemo.float.collect(float, 'RAFOS_VALUES', '', 'combine');
+        if (isempty(rafos_data))
+            rfb_float = false;
+            return;
+        end
+        % apply some corrections
+        hour_scheduled = 30;
+        travel_correction = -60 * (rafos_data(:, 6) - hour_scheduled) ...
+            + rafos_data(:, 7);
+        % the following line throws an error in 2016a
+        % rafos_data(:, 9:2:19) = rafos_data(:, 9:2:19) + travel_correction;
+        rafos_data(:, 9) = rafos_data(:, 9) + travel_correction;
+        rafos_data(:, 11) = rafos_data(:, 11) + travel_correction;
+        rafos_data(:, 13) = rafos_data(:, 13) + travel_correction;
+        rafos_data(:, 15) = rafos_data(:, 15) + travel_correction;
+        rafos_data(:, 17) = rafos_data(:, 17) + travel_correction;
+        rafos_data(:, 19) = rafos_data(:, 19) + travel_correction;
+        % remove all unknown dates
+        rafos_data = rafos_data(isfinite(datenum(rafos_data(:, 2:4))), :);
+        
+        % insert NaN for every missing row (compatibility fix for artoa)
+        % time_axis = datenum( ...
+        %     datestr(FLOAT.launchtime,'dd-mmm-yyyy')) ...
+        %     :p_rfb_config.GENERAL_INFO.artoa_expected_cycle ...
+        %     :datenum(datestr(FLOAT.recovertime,'dd-mmm-yyyy') ...
+        %     - 1 ...
+        %     );
+        time_axis = [ ...
+            datenum(fliplr(float{1}.DEPLOYMENT_INFO.deployment_date)) ...
+            :metadata.RAFOS.GENERAL_INFO.artoa_expected_cycle ...
+            :datenum(float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec(1:3)) ...
+            ];
+        rafos_dates = datenum(rafos_data(:, 2:4));
+        
+        [~, id1] = intersect(time_axis, rafos_dates);
+        [~, id2] = intersect(rafos_dates, time_axis);
+        
+        if (size(rafos_data, 1) > length(time_axis))
+            disp([ mfilename ...
+                ': More RAFOS values available than possible for float number ' ...
+                num2str(float{1}.FLOAT_IDENTIFICATION.float_serial_number) '!' ...
+                ]);
+        end
+        
+        if (strcmp(float{end}.SECOND_ORDER_INFORMATION.best_guess_datevec_src, 'none'))
+            % create resulting matrix
+            RAFOS_VALUES = rafos_data;
+            %RAFOS_VALUES(id1, 1) = id1;
+
+            % change order of columns and remove unnecessary ones
+            tmp = [ ...
+                RAFOS_VALUES(:, 1:19), ...
+                RAFOS_VALUES(:, 22), ...
+                RAFOS_VALUES(:, 20) ...
+                ];
+            RAFOS_VALUES = tmp;
+            clear tmp
+
+            %% Set values that were not available until now
+            FLOAT.phasespercycle = length(rafos_dates) + 1;
+        else
+            % create resulting matrix
+            RAFOS_VALUES = nan(length(time_axis), size(rafos_data, 2));
+            RAFOS_VALUES(id1, :) = rafos_data(id2, :);
+            RAFOS_VALUES(id1, 1) = id1;
+
+            % change order of columns and remove unnecessary ones
+            tmp = [ ...
+                RAFOS_VALUES(:, 1:19), ...
+                RAFOS_VALUES(:, 22), ...
+                RAFOS_VALUES(:, 20) ...
+                ];
+            RAFOS_VALUES = tmp;
+            clear tmp
+
+            %% Set values that were not available until now
+            FLOAT.phasespercycle = length(time_axis) + 1;
+        end
+        
+        %% Write resulting structure
+        
+        rfb_float.FLOAT = FLOAT;
+        rfb_float.VARIABLE_LIST = VARIABLE_LIST;
+        if ~isempty(SAT_DATA)
+            rfb_float.SAT_FORMAT = SAT_FORMAT;
+            rfb_float.SAT_DATA = SAT_DATA;
+        end
+        rfb_float.DATA = RAFOS_VALUES;
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m
new file mode 100644
index 0000000000000000000000000000000000000000..910ab3700db2c03cada53eceb0fe447c88ec5619
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/create_and_save_collection.m
@@ -0,0 +1,49 @@
+function [ boolean, rfb_floats ] = create_and_save_collection( p_nemo_floats, p_config, p_metadata )
+%CREATE_AND_SAVE_COLLECTION Creates the rfb structure from a collection of nemo floats and saves them to disk.
+%   
+%   Parameters:
+%   p_nemo_floats:          The nemo data.
+%   p_config:               The nemo configuration.
+%   p_metadata:             The nemo metadata.
+%
+%   Returns:
+%   boolean:                True, if successful.
+%   rfb_floats:             The rfb structure.
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_floats) || ~isstruct(p_config) || ~iscell(p_metadata))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Collect necessary information and save all rfb files
+
+output_files = cellfun(...
+    @(x) [ p_config.RFB.output_path sprintf('%04d', x{1}.FLOAT_IDENTIFICATION.float_serial_number) '.rfb'] ...
+    , p_nemo_floats, 'UniformOutput', false);
+
+p_metadata = nemo.sync_metadata_to_nemodata(p_nemo_floats, p_metadata);
+
+[rfb_floats, ~] = nemo.rfb.create(p_nemo_floats, p_metadata);
+
+booleans = cellfun(@nemo.rfb.save, rfb_floats, output_files, cell(length(rfb_floats), 1));
+
+if (all(booleans))
+    boolean = true;
+else
+    boolean = false;
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m b/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m
new file mode 100644
index 0000000000000000000000000000000000000000..caa105edf425c58ac8e2c8174d45d94665aa9a1e
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+rfb/save.m
@@ -0,0 +1,174 @@
+function [ boolean ] = save( p_profile, p_output_file, p_overwrite )
+%SAVE Writes the input profile to the output file. 
+%   This function saves the p_profile structure to an ASCII textfile with
+%   .profile extension. The structure of the parameter is being transferred
+%   equally into the textfile so that it can be loaded by the load
+%   function.
+%   TO BE OPTIMIZED: This function does not recursively checks for the
+%   variable type. Only up to the second level.
+%
+%   Returns: Boolean variable, being false if something went wrong during
+%            write.
+%
+%   Parameters:
+%   p_profile:      Contains the profile structure that will be written to
+%                   the output file.
+%   p_output_file:  Absolute file path to the output file that will be
+%                   written.
+%   p_overwrite:    If set to true, the output file will be overwritten if
+%                   it exists.
+%   
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (isa(p_profile, 'logical') && ~p_profile)
+    boolean = true;
+    return
+end
+
+if (~isstruct(p_profile) || ~ischar(p_output_file))
+    parameter_error = true;
+end
+
+if (isempty(p_overwrite))
+    p_overwrite = 1; % default setting is to overwrite existing profile files
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% File existence checks
+
+[directory, ~, ~] = fileparts(p_output_file);
+if exist(directory, 'dir') ~= 7
+    mkdir(directory)
+end
+
+if exist(p_output_file, 'file') == 2 && ~p_overwrite
+    % file will not be overwritten
+    boolean = true;
+    return
+end
+
+%% Write file
+
+% prepare for writing
+help_tabs = ['\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t'];
+desired_tab_count = 12;
+section_names = fieldnames(p_profile); % will be stored in between []
+section_count = length(section_names);
+output_file_id = fopen(p_output_file, 'w'); % open file for writing
+double_format = '%.15g';
+
+% write content
+for o_section = 1:section_count % run through every section
+    
+    % write section name to file
+    fprintf(output_file_id, ['[' section_names{o_section} ']' '\n' ]);
+        
+    % check which type of data it is and decide how to output
+    switch class(p_profile.(section_names{o_section}))
+        case 'struct'
+            write_struct(p_profile.(section_names{o_section}));
+            
+        case 'cell'
+            write_cell(p_profile.(section_names{o_section}));
+            
+        case 'double'
+            write_double(p_profile.(section_names{o_section}));
+            
+        otherwise
+    end
+    
+    fprintf(output_file_id, '\n');
+    
+
+end
+
+% close file
+fclose(output_file_id);
+
+boolean = true; % update return variable
+
+
+    function [ tabs ] = get_tabs_to_insert(p_member_name)
+        % this function computes the required tabs to insert, making the
+        % textfile easier to read.
+        tablength = 4;
+        tab_count = desired_tab_count - floor((length(p_member_name)+1)/tablength);
+        tabs = help_tabs(1:(tab_count*2));
+    end
+
+    function write_struct(p_input_struct)
+        % get all fieldnames and write them to the file
+        member_names = fieldnames(p_input_struct);
+        member_count = length(member_names);
+        for o_member = 1:member_count % run through all members
+            % calculate how many tabs have to be inserted to have a
+            % good table layout
+            tabstring = get_tabs_to_insert(member_names{o_member});
+            fprintf(output_file_id, ['-' member_names{o_member} tabstring]);
+            current_member = p_input_struct.(member_names{o_member});
+            current_member_class = class(current_member);
+            switch current_member_class
+                case {'double', 'logical'}
+                    if (isempty(current_member))
+                        fprintf(output_file_id, [current_member '\n']);
+                    else
+                        write_double(current_member);
+                    end
+                case 'char'
+                    fprintf(output_file_id, [current_member '\n']);
+                otherwise
+                    disp(['Unknown class in file: ' class(current_member)]);
+            end
+
+        end
+        clear o_member
+    end
+
+    function write_cell(p_input_cell)
+        % run through every cell member and check the class
+        for o_cell_line = 1:size(p_input_cell, 1)
+            for o_cell_column = 1:size(p_input_cell, 2)
+                current_value = p_input_cell{o_cell_line, o_cell_column};
+                switch class(current_value)
+                    case 'char'
+                        fprintf(output_file_id, current_value);
+                    case {'double', 'logical'}
+                        fprintf(output_file_id, double_format, current_value);
+                    otherwise
+                        disp(['Unknown class in file: ' class(current_value)]);
+                end
+                if (o_cell_column ~= size(p_input_cell, 2))
+                    fprintf(output_file_id, '\t');
+                end
+            end
+            
+            fprintf(output_file_id, '\n');
+        end
+    end
+
+    function write_double(p_input_double)
+        
+        for o_double = 1:size(p_input_double, 1)
+            if length(p_input_double(o_double, :)) > 1
+                fprintf(output_file_id, sprintf([double_format '\t'], p_input_double(o_double, 1:end-1)));
+            end
+            fprintf(output_file_id, [double_format '\n'], p_input_double(o_double, end));
+        end
+        clear o_double
+        
+    end
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m b/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m
new file mode 100644
index 0000000000000000000000000000000000000000..df6b84082da754723216044c5cfe5aa7f24e8a6b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+sbd/decode_float.m
@@ -0,0 +1,41 @@
+function [ decoded_sbd, sbd_messages_info ] = decode_float( p_config, p_imei )
+%DECODE_FLOAT Decodes the sbd messages of the floats that match the given IMEI.
+%
+%   Parameters:
+%   p_config:           The nemo configuration.
+%   p_imei:             The IMEI of the float to be decoded.
+%
+%   Returns:
+%   decoded_sbd:        The decoded sbd messages.
+%   sbd_messages_info:  The information about the sbd messages.
+
+%% Initialize return value
+decoded_sbd = false;
+sbd_messages_info = false;
+
+%% Create filepath string
+sbd = ['sbd' p_config.GENERAL.folder_separator];
+if (~ischar(p_imei))
+    imei = int2str(p_imei);
+else
+    imei = p_imei;
+end
+    
+filepath = p_config.MAIL.storage_directory;
+
+% Do not change the order of the next two if conditions!
+if (p_config.MAIL.sort_by_imei)
+    filepath = [filepath imei p_config.GENERAL.folder_separator];
+end
+if (p_config.MAIL.separate_sbd_and_txt)
+    filepath = [filepath sbd];
+end
+
+if (p_config.MAIL.sort_by_imei)
+    [decoded_sbd,~,sbd_messages_info] = nemo.optimare.process_moms(filepath);
+else
+    [decoded_sbd,~,sbd_messages_info] = nemo.optimare.process_moms(filepath, imei);
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m b/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m
new file mode 100644
index 0000000000000000000000000000000000000000..f4dbcc24b255ba9a406083adb7b1171e73c37f7f
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+test/best_guess_nan.m
@@ -0,0 +1,23 @@
+function [] = best_guess_nan(bgp_profiles)
+%BEST_GUESS_NAN Tests best guess positions for NaN's.
+
+% get size of bgp_profiles
+float_count = length(bgp_profiles);
+
+for o_floats = 1:float_count
+    profile_count = length(bgp_profiles{o_floats});
+    nan_positions = [];
+    for o_profiles = 1:profile_count
+        if isnan(bgp_profiles{o_floats}{o_profiles}.SECOND_ORDER_INFORMATION.best_guess_position_src)
+            nan_positions = [nan_positions; o_profiles];
+        end
+    end
+    if (~isempty(nan_positions))
+        disp(['Float index: ' num2str(o_floats) ' Float ID: ' num2str(bgp_profiles{o_floats}{1}.FLOAT_IDENTIFICATION.float_serial_number)]);
+        nan_positions
+    end
+    clear nan_positions
+    
+end
+
+end
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m b/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m
new file mode 100644
index 0000000000000000000000000000000000000000..b3eb77f4ae8bc8fe303a71bf56d9b0877fc1dc15
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+test/compare_nemo_data.m
@@ -0,0 +1,16 @@
+function compare_nemo_data(p_first_data, p_second_data)
+%COMPARE_NEMO_DATA Compares two data sets of nemo data for differences.
+
+length_a = length(p_first_data);
+length_b = length(p_second_data);
+
+for o_a = 1:length_a
+    for i = 1:length(p_first_data{o_a})
+        [bool, string] = nemo.profile.compare(p_first_data{o_a}{i}, p_second_data{o_a}{i});
+        if ~bool
+            disp(['float index ' num2str(o_a) ' profile ' num2str(i) ': ' string{:}]);
+        end
+    end
+end
+
+clear o_a i length_a length_b first_floats second_floats
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m b/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m
new file mode 100644
index 0000000000000000000000000000000000000000..b9baf316c3a8de4bdd94b92d4fcf77b1b1189f6b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+test/mail_development.m
@@ -0,0 +1,24 @@
+% config = nemo.load_config('etc\nemo2profile.conf');
+% 
+% imapstore = nemo.mail.connect(config)
+% messages = nemo.mail.fetch_messages(imapstore, 'Inbox');
+% 
+% 
+% msg = messages(end);
+% 
+% 
+% 
+% [from, subject, inline, attachments] = nemo.mail.extract(msg);
+% att = attachments{1,2};
+% 
+% 
+% nemo.mail.export_attachment(att, '\\filesrv1.awi.de\csys_ocean2\argo\data\nemo\mail\sbd.sbd')
+
+config = nemo.load.config('etc\nemo2profile.conf');
+if ~exist('metadata', 'var')
+    %metadata = nemo.load.xml_file('etc\float_metadata');
+    metadata = nemo.load.float_metadata('etc\float_metadata');
+end
+tic;
+nemo.float.fetch_process_mails(config, metadata)
+toc
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m b/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m
new file mode 100644
index 0000000000000000000000000000000000000000..9005b9558f904e66af692c74685cb1032663ad49
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+test/process_nemo.m
@@ -0,0 +1,82 @@
+timer = tic;
+disp([mfilename ': Adding dependencies ...']);
+cd('C:\FLOAT DEPLOYMENT ON POLARSTERN\FLOAT DATA PROCESSING AND ANALYSIS');
+addpath(genpath('C:\FLOAT DEPLOYMENT ON POLARSTERN\FLOAT DATA PROCESSING AND ANALYSIS'));
+
+disp([mfilename ': Loading configuration file...']);
+% load program config
+config = nemo.load.config('etc\nemo2profile.conf');
+disp([mfilename ': ...done!']);
+disp([mfilename ': Loading float metadata files...']);
+% load all floats that have to be processed
+metadata = nemo.load.float_metadata('etc\float_metadata');
+disp([mfilename ': ...done!']);
+
+disp([mfilename ': Downloading and processing mails...']);
+% download and process mails
+nemo.float.fetch_process_mails(config, metadata);
+disp([mfilename ': ...done!']);
+disp([mfilename ': Converting sbd attachments and extracting transmission information...']);
+awi_struct = nemo.load.floats2nemostruct(config, metadata);
+disp([mfilename ': ...done!']);
+
+%% Remove logical cells (without profiles)
+awi_struct = awi_struct(~cellfun(@(x) isa(x, 'logical'), awi_struct));
+
+%% Manual quality check
+awi_struct = nemo.postprocessing.delete_profile(awi_struct, 239, 1);
+
+%% Process data
+
+disp([mfilename ': Creating second order information...']);
+% possible estimation methods:
+% 'time based': memory consumption EXTREMLY high, but most descriptive
+% 'position based': less memory consumption, positions equally split
+% 'nan': most leightweight estimation, using NaN for unknown positions
+awi_struct = ...
+    nemo.postprocessing.position.create_best_guess( ...
+    awi_struct, 'time based' ...
+    );
+awi_struct = nemo.postprocessing.datetime.create_best_guess(awi_struct);
+disp([mfilename ': ... done!' ]);
+disp([mfilename ': Doing quality check...' ]);
+awi_struct = nemo.postprocessing.quality_check(awi_struct);
+disp([mfilename ': ... done!']);
+disp([mfilename ': Generating RFB files and save to disk...']);
+[success, rfb_floats] = nemo.rfb.create_and_save_collection(awi_struct, config, metadata);
+if (~success)
+    disp([mfilename ': ... failed!']);
+else
+    save([config.OUTPUT_FILES.output_path 'rfb_floats.mat'], 'rfb_floats');
+    disp([mfilename ': ... done!']);
+end
+disp([mfilename ': Saving data to ' config.OUTPUT_FILES.output_path '...']);
+nemo.save_data(awi_struct, config);
+save([config.OUTPUT_FILES.output_path 'awi_struct.mat'], 'awi_struct');
+save([config.OUTPUT_FILES.output_path 'config.mat'], 'config');
+save([config.OUTPUT_FILES.output_path 'metadata.mat'], 'metadata');
+disp([mfilename ': ... done!']);
+    
+disp([mfilename ': Generating analysis items...']);
+if ~nemo.analysis.create_csv_table(awi_struct, config)
+    warning('Creating overview table failed!');
+end
+if ~nemo.plot.floats(awi_struct, config, 'south', false)
+    warning('Creating overview south plot failed!');
+end
+if ~nemo.plot.floats(awi_struct, config, 'south', true)
+    warning('Creating plot for every single float failed!');
+end
+if ~nemo.plot.arrival_of_profiles(awi_struct, config)
+    warning('Creating arrival of profiles plot failed!');
+end
+disp([mfilename ': ... done!']);
+
+disp([mfilename ': Uploading float data to server ...']);
+% Upload data to server
+if (~nemo.ftp.upload(config))
+    disp([mfilename ': Uploading data failed!']);
+else
+    disp([mfilename ': Uploading data successful!']);
+end
+disp([mfilename ': Processing nemo data took ' num2str(toc(timer)) ' seconds!']);
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m b/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..a125704374e484bd6d16ddc8144d55ca6c28a43c
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/+test/sbd2nemostruct.m
@@ -0,0 +1,7 @@
+config = nemo.load.config('etc\nemo2profile.conf');
+if ~exist('metadata', 'var')
+    %metadata = nemo.load.xml_file('etc\float_metadata');
+    metadata = nemo.load.float_metadata('etc\float_metadata\sn_248_imei_300234011429370_metadata.xml');
+end
+
+nemo_struct = nemo.load.floats2nemostruct(config, metadata);
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m b/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..4911163b70c1754459288b26df70d4fdbecaee10
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/find_float_in_metadata.m
@@ -0,0 +1,50 @@
+function [ float_metadata ] = find_float_in_metadata( p_metadata, p_imeis )
+%FIND_FLOAT_IN_METADATA Returns the metadata of the given IMEI
+%   Searches p_metadata for the given IMEI.
+%
+%   Parameters:
+%   p_metadata:                 The metadata of the floats.
+%   p_imeis:                    The IMEI(s) to be searched for.
+%
+%   Returns:
+%   float_metadata:             False if not found. Returns copies of the float
+%                               metadata of the given IMEIs otherwise.
+
+%% Initialize return value
+float_metadata = false;
+
+%% Find the float
+
+if (isempty(p_imeis))
+    return;
+end
+booleans = cellfun(@(x) x.GENERAL_INFO.IMEI == p_imeis(1), p_metadata);
+for i=2:length(p_imeis)
+    tmp_booleans = cellfun(@(x) x.GENERAL_INFO.IMEI == p_imeis(i), p_metadata);
+    booleans = booleans + tmp_booleans;
+end
+
+booleans(booleans > 1) = 1;
+booleans = logical(booleans);
+
+float_metadata = p_metadata(booleans);
+
+if (~isempty(float_metadata))
+    return
+end
+
+% nothing has been found, so check all serials
+
+booleans = cellfun(@(x) x.GENERAL_INFO.SERIAL == p_imeis(1), p_metadata);
+for i=2:length(p_imeis)
+    tmp_booleans = cellfun(@(x) x.GENERAL_INFO.SERIAL == p_imeis(i), p_metadata);
+    booleans = booleans + tmp_booleans;
+end
+
+booleans(booleans > 1) = 1;
+booleans = logical(booleans);
+
+float_metadata = p_metadata(booleans);
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m b/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m
new file mode 100644
index 0000000000000000000000000000000000000000..e650a352e905fc94852190053850b3814d7489fb
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/get_data_with_setting.m
@@ -0,0 +1,35 @@
+function [ metadata, nemo_data ] = get_data_with_setting( p_metadata, p_nemo_data, p_setting_name, p_setting_value )
+%GET_DATA_FOR_OVERVIEW_PLOTS Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Parameter check
+if ~iscell(p_metadata) || ~iscell(p_nemo_data) || ~ischar(p_setting_name) || ~exist('p_setting_value', 'var')
+    disp([mfilename ': Please check your input!']);
+end
+
+%% Get data that matches
+
+
+metadata = cellfun(@(x) match_float(x), p_metadata, 'UniformOutput', false);
+metadata = metadata(cellfun(@(x) ~isempty(x), metadata));
+nemo_data = cellfun(@(x) match_float_to_metadata(x), p_nemo_data, 'UniformOutput', false);
+nemo_data = nemo_data(cellfun(@(x) ~isempty(x), nemo_data));
+
+
+    function [ x ] = match_float(meta)
+        x = [];
+        if meta.SETTINGS.(p_setting_name) == p_setting_value
+            x = meta;
+        end
+    end
+
+    function [ y ] = match_float_to_metadata(x)
+        if (isempty(nemo.find_float_in_metadata(metadata, x{1}.FLOAT_IDENTIFICATION.float_serial_number)))
+            y = [];
+        else
+            y = x;
+        end
+    end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m
new file mode 100644
index 0000000000000000000000000000000000000000..b0c0abbef5dc51de29d6112ea9e01556d450ce4d
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_polygon.m
@@ -0,0 +1,88 @@
+function [ filtered_nemo_data ] = get_profiles_in_polygon( p_nemo_data, ...
+    p_xv, p_yv )
+%GET_PROFILES_IN_POLYGON Returns all profiles in the given polygon.
+%   This function returns all profiles that have been found in the given
+%   polygon. The check is being performed with the built in MATLAB function
+%   inpolygon. Please check documentation to be sure p_xv and p_yv is being
+%   set correctly. The structure of p_nemo_data is being held, all profiles
+%   that are not in the polygon are being removed. If p_nemo_data is a
+%   string, it reads the profiles using nemo.load_data().
+%
+%   Returns: p_nemo_data with filtered profiles, false on fail.
+%
+%   Requirements:
+%   - read_profiles.m: Containing function that reads nemo profiles from a
+%       file into a multidimensional struct.
+%
+%   Parameters:
+%   p_nemo_data:    Either has to be a multidimensional struct (created by
+%                   nemo.load.data()) or a string, where the nemo float data
+%                   is stored.
+%   p_xv, p_yv:     X and Y coordinates of the polygon. To get examples how
+%                   they should be set up, see "help inpolygon"
+
+
+%% Initialize return variables
+
+filtered_nemo_data = {};
+
+%% Check parameters
+parameter_error = false;
+
+if (ischar(p_nemo_data)) % read profiles using read_profiles.m
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % read profiles
+        p_nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    end
+end
+
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by read_profiles.m!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+
+%% Delete all profiles that are not in polygon
+
+%nemo_field_names = fieldnames(p_nemo_data); % get all float names
+
+for index_current_float = 1:length(p_nemo_data) % process every single float
+    current_profiles = p_nemo_data{index_current_float};
+    profile_count = length(current_profiles);
+    filtered_profiles = {};
+    
+    % loop through current_profiles
+    index_current_profile = 1;
+    while (index_current_profile <= profile_count)
+        % get position
+        xq = current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_position_lon;
+        yq = current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_position_lat;
+        
+        if (inpolygon(xq, yq, p_xv, p_yv))
+            % profile is not in given rectangle, so delete it
+            filtered_profiles{end + 1} = current_profiles{index_current_profile};
+        end
+        
+        % increase profile index
+        index_current_profile = index_current_profile + 1;
+    end
+    
+    % update return variable
+    if (~isempty(filtered_profiles)) % there are profiles to update
+        filtered_nemo_data{end + 1} = filtered_profiles;
+    end
+    
+    clear current_profiles profile_field_names profile_count ...
+        index_current_profile;
+    
+end
+
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m
new file mode 100644
index 0000000000000000000000000000000000000000..b38703b09d987bea2258b23566713bb0d28ecf71
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/get_profiles_in_timerange.m
@@ -0,0 +1,87 @@
+function [ filtered_nemo_data ] = get_profiles_in_timerange( p_nemo_data, ...
+    p_tlower, p_tupper )
+%GET_PROFILES_IN_TIMERANGE Returns all profiles in the given time range.
+%   This function returns all profiles that have been found in the given
+%   time range. The check is being performed with the built in MATLAB function
+%   isbetween. Please check documentation to be sure p_tlower and p_tupper is being
+%   set correctly. The structure of p_nemo_data is being held, all profiles
+%   that are not in the timrange are being removed. If p_nemo_data is a
+%   string, it reads the profiles using nemo.load_data().
+%
+%   Returns: p_nemo_data with filtered profiles, false on fail.
+%
+%   Requirements:
+%   - read_profiles.m: Containing function that reads nemo profiles from a
+%       file into a multidimensional struct.
+%
+%   Parameters:
+%   p_nemo_data:    Either has to be a multidimensional struct (created by
+%                   nemo.load.data()) or a string, where the nemo float data
+%                   is stored.
+%   p_tlower, p_tupper:     Lower and upper time range boundary. To get examples how
+%                   they should be set up, see "help inpolygon"
+
+
+%% Initialize return variables
+
+filtered_nemo_data = {};
+
+%% Check parameters
+parameter_error = false;
+
+if (ischar(p_nemo_data)) % read profiles using read_profiles.m
+    if (~isdir(p_nemo_data)) % parameter is not a directory
+        parameter_error = true;
+    else % read profiles
+        p_nemo_data = nemo.load.data(p_nemo_data);
+    end
+else if (~iscell(p_nemo_data)) % raise error
+        parameter_error = true;
+    end
+end
+
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please provide either a ' ...
+        'directory path or a struct created by read_profiles.m!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+
+%% Delete all profiles that are not in polygon
+
+%nemo_field_names = fieldnames(p_nemo_data); % get all float names
+
+for index_current_float = 1:length(p_nemo_data) % process every single float
+    current_profiles = p_nemo_data{index_current_float};
+    profile_count = length(current_profiles);
+    filtered_profiles = {};
+    
+    % loop through current_profiles
+    index_current_profile = 1;
+    while (index_current_profile <= profile_count)
+        % get position
+        profile_time = datetime(current_profiles{index_current_profile}.SECOND_ORDER_INFORMATION.best_guess_datevec);
+        
+        if (isbetween(profile_time, p_tlower, p_tupper))
+            % profile is not in given rectangle, so delete it
+            filtered_profiles{end + 1} = current_profiles{index_current_profile};
+        end
+        
+        % increase profile index
+        index_current_profile = index_current_profile + 1;
+    end
+    
+    % update return variable
+    if (~isempty(filtered_profiles)) % there are profiles to update
+        filtered_nemo_data{end + 1} = filtered_profiles;
+    end
+    
+    clear current_profiles profile_field_names profile_count ...
+        index_current_profile;
+    
+end
+
+end
diff --git a/lib/vendor/nemo2profilelib/+nemo/save_data.m b/lib/vendor/nemo2profilelib/+nemo/save_data.m
new file mode 100644
index 0000000000000000000000000000000000000000..4841142f8634d03e0cddd846168a9dad594ef442
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/save_data.m
@@ -0,0 +1,101 @@
+function [ boolean ] = save_data( p_nemo_data, p_config )
+%SAVE_DATA Writes the input nemo data using the given config.
+%   This function saves given nemo data using nemo.pofile.save(). All
+%   required options have to be stored in p_config.OUTPUT_FILES.
+%
+%   Returns: Boolean variable, being false if something went wrong during
+%            write.
+%
+%   Parameters:
+%   p_nemo_data:  Contains structure with all float and profile information.
+%   p_config:     Contains all config options that are required, such as
+%                 output path as well as filename format (currently not
+%                 implemented) of profile files.
+%   
+
+%% Initialize return variables
+boolean = false;
+
+%% Parameter check
+parameter_error = false;
+
+if (~iscell(p_nemo_data) || ~isstruct(p_config))
+    parameter_error = true;
+end
+
+if (parameter_error) % display error message
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+else % do some cleanup
+    clear parameter_error;
+end
+
+%% Save all profiles
+
+output_folder = p_config.OUTPUT_FILES.output_path;
+header_filename = p_config.OUTPUT_FILES.profile_header_file;
+
+if (~exist(header_filename, 'file'))
+    warning('Specified header file does not exist. Please check your configuration!');
+    return;
+end
+
+float_count = length(p_nemo_data);
+
+if (~exist(output_folder, 'dir'))
+    if (~mkdir(output_folder))
+        warning('Could not create output folder for nemo profiles!');
+        return;
+    end
+end
+
+for o_float = 1:float_count % every float will be processed
+    profile_count = length(p_nemo_data{o_float});
+    profile_subfoldername = sprintf('%04d', p_nemo_data{o_float}{1}.FLOAT_IDENTIFICATION.float_serial_number);
+    profile_folder = [output_folder profile_subfoldername p_config.GENERAL.folder_separator];
+    
+    % check if folder exists
+    if (~exist(profile_folder, 'dir'))
+        if (~mkdir(profile_folder))
+            warning(['Could not create output subfolder for nemo float number ' profile_subfoldername '!']);
+            return;
+        end
+    end
+    
+    for o_profile = 1:profile_count
+        % construct desired profile filename
+        profile_number = sprintf('%04d', p_nemo_data{o_float}{o_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number);
+        if isnan(p_nemo_data{o_float}{o_profile}.PROFILE_TECHNICAL_DATA.xmit_profile_number)
+            disp([mfilename ': Float ' profile_subfoldername ...
+                ' contains a profile without xmit_profile_number!']);
+            continue
+        end
+        output_filename = [profile_folder profile_subfoldername '_' ...
+            profile_number '.profile'];
+        
+        overwrite_file = 1; % if true, existing files will be overwritten
+        if (p_config.OUTPUT_FILES.update_changed_files_only) ...
+                && exist(output_filename, 'file')
+            % load existing profile
+            old_profile = nemo.profile.load(output_filename);
+            if nemo.profile.compare(old_profile, ...
+                    p_nemo_data{o_float}{o_profile})
+                % profiles are the same, so do not overwrite current one
+                overwrite_file = 0;
+            end
+            clear old_profile
+        end
+        
+        if ~nemo.profile.save(header_filename, ...
+                p_nemo_data{o_float}{o_profile}, output_filename, overwrite_file)
+            warning(['Writing profile ' profile_number ' of float with serial ' profile_subfoldername ' failed!' ]);
+        end
+    end
+end
+
+boolean = true;
+
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m b/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m
new file mode 100644
index 0000000000000000000000000000000000000000..de5ad4a82b70f14016f26245e59c2473a52cf76b
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/save_startup_messages.m
@@ -0,0 +1,29 @@
+function [ boolean ] = save_startup_messages( p_config, p_startup_messages )
+%SAVE_STARTUP_MESSAGES Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+if ~isstruct(p_config) || ~iscell(p_startup_messages)
+    disp([ mfilename ': Wrong parameter! Please check your input parameters carefully!']);
+    return;
+end
+
+%% Save all messages to the specified folder
+if ~isfield(p_config.OUTPUT_FILES, 'output_path_startup_messages')
+    disp([ mfilename ': Parameter output_path_startup_messages not specified!']);
+end
+
+output_folder = p_config.OUTPUT_FILES.output_path_startup_messages;
+
+% remove empty cells if there are some
+p_startup_messages = p_startup_messages(~cellfun(@(x) isempty(x), p_startup_messages));
+
+boolean = all(cellfun(@(x) nemo.float.save_startup_message(x, output_folder), p_startup_messages));
+
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m b/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m
new file mode 100644
index 0000000000000000000000000000000000000000..5f98045b585901f57212dc93b82e89988c01180a
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/sort_data_by_float_serial.m
@@ -0,0 +1,36 @@
+function [ sorted_nemo_data ] = sort_data_by_float_serial( p_nemo_data )
+%SORT_DATA_BY_FLOAT_SERIAL Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize return variable
+sorted_nemo_data = {};
+
+%% Parameter check
+if ~iscell(p_nemo_data)
+    disp([mfilename ': Please check your input parameter!']);
+    return;
+end
+
+%% Sort by float serial
+
+float_serials = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.float_serial_number, p_nemo_data);
+
+[~, indices] = sort(float_serials);
+% create helping variable to check if the float is already sorted
+sorted = [1:length(float_serials)];
+if indices == sorted
+    % indeed it is, so save work and just return the input one :)
+    sorted_nemo_data = p_nemo_data;
+    return
+end
+
+% sorting required
+sorted_nemo_data = {};
+
+% indices have already been taken so just run through them
+for i = 1:length(p_nemo_data)
+    sorted_nemo_data{i} = p_nemo_data{indices(i)};
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m b/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m
new file mode 100644
index 0000000000000000000000000000000000000000..2c374110a128bdc4a31ac81e6b564dc777437f8e
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/sync_metadata_to_nemodata.m
@@ -0,0 +1,16 @@
+function [ metadata ] = sync_metadata_to_nemodata( p_nemo_data, p_metadata )
+%SYNC_METADATA_NEMODATA Summary of this function goes here
+%   Detailed explanation goes here
+
+
+%% Initialize return variables
+metadata = {};
+
+%% Get all IMEIS of the floats of nemo data
+
+imeis = cellfun(@(x) x{1}.FLOAT_IDENTIFICATION.transmission_id_number_dec, p_nemo_data);
+metadata = nemo.find_float_in_metadata(p_metadata, imeis);
+
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m b/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m
new file mode 100644
index 0000000000000000000000000000000000000000..6c01f04b9bd8410d140e95384bf835cb0ddd12a1
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+nemo/update_nemo_data_mat.m
@@ -0,0 +1,44 @@
+function [ boolean ] = update_nemo_data_mat( p_nemo_data_filename, p_updated_floats )
+%UPDATE_NEMO_DATA_MAT Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize return variable
+boolean = false;
+
+%% Parameter check
+if ~ischar(p_nemo_data_filename) || ~iscell(p_updated_floats)
+    disp([mfilename 'Please check input parameter!']);
+    return
+end
+
+%% Load mat file
+if exist(p_nemo_data_filename, 'file') == 2
+    n = load(p_nemo_data_filename);
+    nemo_data = n.nemo_data;
+    clear n
+else
+    nemo_data = {};
+end
+
+%% Replace updated floats in nemo data
+for i = 1:length(p_updated_floats)
+    [~, index] = nemo.float.find(nemo_data, p_updated_floats{1}{1}.FLOAT_IDENTIFICATION.float_serial_number);
+    if index == 0 % float is not present
+        nemo_data{end + 1} = p_updated_floats{1};
+    else
+        nemo_data{index} = p_updated_floats{1};
+        p_updated_floats(1) = [];
+    end
+end
+
+%% Sort floats by serial number
+
+nemo_data = nemo.sort_data_by_float_serial(nemo_data);
+
+%% Save to file
+save(p_nemo_data_filename, 'nemo_data');
+
+boolean = true;
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/+utils/suplabel.m b/lib/vendor/nemo2profilelib/+utils/suplabel.m
new file mode 100644
index 0000000000000000000000000000000000000000..7845259a8c1e62317e2acbd4b36effc23466032a
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+utils/suplabel.m
@@ -0,0 +1,101 @@
+function [ax,h]=suplabel(text,whichLabel,supAxes)
+% PLaces text as a title, xlabel, or ylabel on a group of subplots.
+% Returns a handle to the label and a handle to the axis.
+%  [ax,h]=suplabel(text,whichLabel,supAxes)
+% returns handles to both the axis and the label.
+%  ax=suplabel(text,whichLabel,supAxes)
+% returns a handle to the axis only.
+%  suplabel(text) with one input argument assumes whichLabel='x'
+%
+% whichLabel is any of 'x', 'y', 'yy', or 't', specifying whether the 
+% text is to be the xlable, ylabel, right side y-label, 
+% or title respectively.
+%
+% supAxes is an optional argument specifying the Position of the 
+%  "super" axes surrounding the subplots. 
+%  supAxes defaults to [.08 .08 .84 .84]
+%  specify supAxes if labels get chopped or overlay subplots
+%
+% EXAMPLE:
+%  subplot(2,2,1);ylabel('ylabel1');title('title1')
+%  subplot(2,2,2);ylabel('ylabel2');title('title2')
+%  subplot(2,2,3);ylabel('ylabel3');xlabel('xlabel3')
+%  subplot(2,2,4);ylabel('ylabel4');xlabel('xlabel4')
+%  [ax1,h1]=suplabel('super X label');
+%  [ax2,h2]=suplabel('super Y label','y');
+%  [ax3,h2]=suplabel('super Y label (right)','yy');
+%  [ax4,h3]=suplabel('super Title'  ,'t');
+%  set(h3,'FontSize',30)
+%
+% SEE ALSO: text, title, xlabel, ylabel, zlabel, subplot,
+%           suptitle (Matlab Central)
+
+% Author: Ben Barrowes <barrowes@alum.mit.edu>
+
+%modified 3/16/2010 by IJW to make axis behavior re "zoom" on exit same as
+%at beginning. Requires adding tag to the invisible axes
+
+
+currax=findobj(gcf,'type','axes','-not','tag','suplabel');
+
+if nargin < 3
+ supAxes=[.08 .08 .84 .84];
+ ah=findall(gcf,'type','axes');
+ if ~isempty(ah)
+  supAxes=[inf,inf,0,0];
+  leftMin=inf;  bottomMin=inf;  leftMax=0;  bottomMax=0;
+  axBuf=.04;
+  set(ah,'units','normalized')
+  ah=findall(gcf,'type','axes');
+  for ii=1:length(ah)
+   if strcmp(get(ah(ii),'Visible'),'on')
+    thisPos=get(ah(ii),'Position');
+    leftMin=min(leftMin,thisPos(1));
+    bottomMin=min(bottomMin,thisPos(2));
+    leftMax=max(leftMax,thisPos(1)+thisPos(3));
+    bottomMax=max(bottomMax,thisPos(2)+thisPos(4));
+   end
+  end
+  supAxes=[leftMin-axBuf,bottomMin-axBuf,leftMax-leftMin+axBuf*2,bottomMax-bottomMin+axBuf*2];
+ end
+end
+if nargin < 2, whichLabel = 'x';  end
+if nargin < 1, help(mfilename); return; end
+
+if ~isstr(text) | ~isstr(whichLabel)
+  error('text and whichLabel must be strings')
+end
+whichLabel=lower(whichLabel);
+
+ax=axes('Units','Normal','Position',supAxes,'Visible','off','tag','suplabel');
+if strcmp('t',whichLabel)
+  set(get(ax,'Title'),'Visible','on')
+  title(text);
+elseif strcmp('x',whichLabel)
+  set(get(ax,'XLabel'),'Visible','on')
+  xlabel(text);
+elseif strcmp('y',whichLabel)
+  set(get(ax,'YLabel'),'Visible','on')
+  ylabel(text);
+elseif strcmp('yy',whichLabel)
+  set(get(ax,'YLabel'),'Visible','on')
+  ylabel(text);
+  set(ax,'YAxisLocation','right')
+end
+
+%for k=1:length(currax), axes(currax(k));end % restore all other axes
+
+if (nargout < 2)
+  return
+end
+if strcmp('t',whichLabel)
+  h=get(ax,'Title');
+  set(h,'VerticalAlignment','middle')
+elseif strcmp('x',whichLabel)
+  h=get(ax,'XLabel');
+elseif strcmp('y',whichLabel) | strcmp('yy',whichLabel)
+  h=get(ax,'YLabel');
+end
+
+%%%ah=findall(gcf,'type','axes');
+%%%'sssssssss',kb
\ No newline at end of file
diff --git a/lib/vendor/nemo2profilelib/+utils/xml_read.m b/lib/vendor/nemo2profilelib/+utils/xml_read.m
new file mode 100644
index 0000000000000000000000000000000000000000..845efd0724da29f10dfbb59d21180710c51b0df6
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+utils/xml_read.m
@@ -0,0 +1,550 @@
+function [tree, RootName, DOMnode] = xml_read(xmlfile, Pref)
+%XML_READ reads xml files and converts them into Matlab's struct tree.
+%
+% DESCRIPTION
+% tree = xml_read(xmlfile) reads 'xmlfile' into data structure 'tree'
+%
+% tree = xml_read(xmlfile, Pref) reads 'xmlfile' into data structure 'tree'
+% according to your preferences
+%
+% [tree, RootName, DOMnode] = xml_read(xmlfile) get additional information
+% about XML file
+%
+% INPUT:
+%  xmlfile	URL or filename of xml file to read
+%  Pref     Preferences:
+%    Pref.ItemName - default 'item' - name of a special tag used to itemize
+%                    cell arrays
+%    Pref.ReadAttr - default true - allow reading attributes
+%    Pref.ReadSpec - default true - allow reading special nodes
+%    Pref.Str2Num  - default 'smart' - convert strings that look like numbers
+%                   to numbers. Options: "always", "never", and "smart"
+%    Pref.KeepNS   - default true - keep or strip namespace info
+%    Pref.NoCells  - default true - force output to have no cell arrays
+%    Pref.Debug    - default false - show mode specific error messages
+%    Pref.NumLevels- default infinity - how many recursive levels are
+%      allowed. Can be used to speed up the function by prunning the tree.
+%    Pref.RootOnly - default true - output variable 'tree' corresponds to
+%      xml file root element, otherwise it correspond to the whole file.
+%    Pref.CellItem - default 'true' - leave 'item' nodes in cell notation.
+% OUTPUT:
+%  tree         tree of structs and/or cell arrays corresponding to xml file
+%  RootName     XML tag name used for root (top level) node.
+%               Optionally it can be a string cell array storing: Name of
+%               root node, document "Processing Instructions" data and
+%               document "comment" string
+%  DOMnode      output of xmlread
+%
+% DETAILS:
+% Function xml_read first calls MATLAB's xmlread function and than
+% converts its output ('Document Object Model' tree of Java objects)
+% to tree of MATLAB struct's. The output is in format of nested structs
+% and cells. In the output data structure field names are based on
+% XML tags, except in cases when tags produce illegal variable names.
+%
+% Several special xml node types result in special tags for fields of
+% 'tree' nodes:
+%  - node.CONTENT - stores data section of the node if other fields are
+%    present. Usually data section is stored directly in 'node'.
+%  - node.ATTRIBUTE.name - stores node's attribute called 'name'.
+%  - node.COMMENT - stores node's comment section (string). For global
+%    comments see "RootName" output variable.
+%  - node.CDATA_SECTION - stores node's CDATA section (string).
+%  - node.PROCESSING_INSTRUCTIONS - stores "processing instruction" child
+%    node. For global "processing instructions" see "RootName" output variable.
+%  - other special node types like: document fragment nodes, document type
+%   nodes, entity nodes, notation nodes and processing instruction nodes
+%   will be treated like regular nodes
+%
+% EXAMPLES:
+%   MyTree=[];
+%   MyTree.MyNumber = 13;
+%   MyTree.MyString = 'Hello World';
+%   xml_write('test.xml', MyTree);
+%   [tree treeName] = xml_read ('test.xml');
+%   disp(treeName)
+%   gen_object_display()
+%   % See also xml_examples.m
+%
+% See also:
+%   xml_write, xmlread, xmlwrite
+%
+% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com
+% References:
+%  - Function inspired by Example 3 found in xmlread function.
+%  - Output data structures inspired by xml_toolbox structures.
+
+%% default preferences
+DPref.TableName  = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays
+DPref.ItemName  = 'item'; % name of a special tag used to itemize 1D cell arrays
+DPref.CellItem  = false;  % leave 'item' nodes in cell notation
+DPref.ReadAttr  = true;   % allow reading attributes
+DPref.ReadSpec  = true;   % allow reading special nodes: comments, CData, etc.
+DPref.KeepNS    = true;   % Keep or strip namespace info
+DPref.Str2Num   = 'smart';% convert strings that look like numbers to numbers
+DPref.NoCells   = true;   % force output to have no cell arrays
+DPref.NumLevels = 1e10;   % number of recurence levels
+DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings?
+RootOnly        = true;   % return root node  with no top level special nodes
+Debug           = false;  % show specific errors (true) or general (false)?
+tree            = [];
+RootName        = [];
+
+%% Check Matlab Version
+v = ver('MATLAB');
+version = str2double(regexp(v.Version, '\d.\d','match','once'));
+if (version<7.1)
+  error('Your MATLAB version is too old. You need version 7.1 or newer.');
+end
+
+%% read user preferences
+if (nargin>1)
+  if (isfield(Pref, 'TableName')), DPref.TableName = Pref.TableName; end
+  if (isfield(Pref, 'ItemName' )), DPref.ItemName  = Pref.ItemName;  end
+  if (isfield(Pref, 'CellItem' )), DPref.CellItem  = Pref.CellItem;  end
+  if (isfield(Pref, 'Str2Num'  )), DPref.Str2Num   = Pref.Str2Num ;  end
+  if (isfield(Pref, 'NoCells'  )), DPref.NoCells   = Pref.NoCells ;  end
+  if (isfield(Pref, 'NumLevels')), DPref.NumLevels = Pref.NumLevels; end
+  if (isfield(Pref, 'ReadAttr' )), DPref.ReadAttr  = Pref.ReadAttr;  end
+  if (isfield(Pref, 'ReadSpec' )), DPref.ReadSpec  = Pref.ReadSpec;  end
+  if (isfield(Pref, 'KeepNS'   )), DPref.KeepNS    = Pref.KeepNS;    end
+  if (isfield(Pref, 'RootOnly' )), RootOnly        = Pref.RootOnly;  end
+  if (isfield(Pref, 'Debug'    )), Debug           = Pref.Debug   ;  end
+  if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end
+end
+if ischar(DPref.Str2Num), % convert from character description to numbers
+  DPref.Str2Num = find(strcmpi(DPref.Str2Num, {'never', 'smart', 'always'}))-1;
+  if isempty(DPref.Str2Num), DPref.Str2Num=1; end % 1-smart by default
+end
+
+%% read xml file using Matlab function
+if isa(xmlfile, 'org.apache.xerces.dom.DeferredDocumentImpl');
+  % if xmlfile is a DOMnode than skip the call to xmlread
+  try
+    try
+      DOMnode = xmlfile;
+    catch ME
+      error('Invalid DOM node: \n%s.', getReport(ME));
+    end
+  catch %#ok<CTCH> catch for mablab versions prior to 7.5
+    error('Invalid DOM node. \n');
+  end
+else         % we assume xmlfile is a filename
+  if (Debug) % in debuging mode crashes are allowed
+    DOMnode = xmlread(xmlfile);
+  else       % in normal mode crashes are not allowed
+    try
+      try
+        DOMnode = xmlread(xmlfile);
+      catch ME
+        error('Failed to read XML file %s: \n%s',xmlfile, getReport(ME));
+      end
+    catch %#ok<CTCH> catch for mablab versions prior to 7.5
+      error('Failed to read XML file %s\n',xmlfile);
+    end
+  end
+end
+Node = DOMnode.getFirstChild;
+
+%% Find the Root node. Also store data from Global Comment and Processing
+%  Instruction nodes, if any.
+GlobalTextNodes = cell(1,3);
+GlobalProcInst  = [];
+GlobalComment   = [];
+GlobalDocType   = [];
+while (~isempty(Node))
+  if (Node.getNodeType==Node.ELEMENT_NODE)
+    RootNode=Node;
+  elseif (Node.getNodeType==Node.PROCESSING_INSTRUCTION_NODE)
+    data   = strtrim(char(Node.getData));
+    target = strtrim(char(Node.getTarget));
+    GlobalProcInst = [target, ' ', data];
+    GlobalTextNodes{2} = GlobalProcInst;
+  elseif (Node.getNodeType==Node.COMMENT_NODE)
+    GlobalComment = strtrim(char(Node.getData));
+    GlobalTextNodes{3} = GlobalComment;
+    %   elseif (Node.getNodeType==Node.DOCUMENT_TYPE_NODE)
+    %     GlobalTextNodes{4} = GlobalDocType;
+  end
+  Node = Node.getNextSibling;
+end
+
+%% parse xml file through calls to recursive DOMnode2struct function
+if (Debug)   % in debuging mode crashes are allowed
+  [tree RootName] = DOMnode2struct(RootNode, DPref, 1);
+else         % in normal mode crashes are not allowed
+  try
+    try
+      [tree RootName] = DOMnode2struct(RootNode, DPref, 1);
+    catch ME
+      error('Unable to parse XML file %s: \n %s.',xmlfile, getReport(ME));
+    end
+  catch %#ok<CTCH> catch for mablab versions prior to 7.5
+    error('Unable to parse XML file %s.',xmlfile);
+  end
+end
+
+%% If there were any Global Text nodes than return them
+if (~RootOnly)
+  if (~isempty(GlobalProcInst) && DPref.ReadSpec)
+    t.PROCESSING_INSTRUCTION = GlobalProcInst;
+  end
+  if (~isempty(GlobalComment) && DPref.ReadSpec)
+    t.COMMENT = GlobalComment;
+  end
+  if (~isempty(GlobalDocType) && DPref.ReadSpec)
+    t.DOCUMENT_TYPE = GlobalDocType;
+  end
+  t.(RootName) = tree;
+  tree=t;
+end
+if (~isempty(GlobalTextNodes))
+  GlobalTextNodes{1} = RootName;
+  RootName = GlobalTextNodes;
+end
+
+
+%% =======================================================================
+%  === DOMnode2struct Function ===========================================
+%  =======================================================================
+function [s TagName LeafNode] = DOMnode2struct(node, Pref, level)
+
+%% === Step 1: Get node name and check if it is a leaf node ==============
+[TagName LeafNode] = NodeName(node, Pref.KeepNS);
+s = []; % initialize output structure
+
+%% === Step 2: Process Leaf Nodes (nodes with no children) ===============
+if (LeafNode)
+  if (LeafNode>1 && ~Pref.ReadSpec), LeafNode=-1; end % tags only so ignore special nodes
+  if (LeafNode>0) % supported leaf node types
+    try
+      try         % use try-catch: errors here are often due to VERY large fields (like images) that overflow java memory
+        s = char(node.getData);
+        if (isempty(s)), s = ' '; end                              % make it a string
+        % for some reason current xmlread 'creates' a lot of empty text
+        % fields with first chatacter=10 - those will be deleted.
+        if (~Pref.PreserveSpace || s(1)==10) 
+          if (isspace(s(1)) || isspace(s(end))), s = strtrim(s); end % trim speces is any
+        end
+        if (LeafNode==1), s=str2var(s, Pref.Str2Num, 0); end       % convert to number(s) if needed
+      catch ME    % catch for mablab versions 7.5 and higher
+        warning('xml_io_tools:read:LeafRead', ...
+          'This leaf node could not be read and was ignored. ');
+        getReport(ME)
+      end
+    catch         %#ok<CTCH> catch for mablab versions prior to 7.5
+      warning('xml_io_tools:read:LeafRead', ...
+        'This leaf node could not be read and was ignored. ');
+    end
+  end
+  if (LeafNode==3) % ProcessingInstructions need special treatment
+    target = strtrim(char(node.getTarget));
+    s = [target, ' ', s];
+  end
+  return % We are done the rest of the function deals with nodes with children
+end
+if (level>Pref.NumLevels+1), return; end % if Pref.NumLevels is reached than we are done
+
+%% === Step 3: Process nodes with children ===============================
+if (node.hasChildNodes)        % children present
+  Child  = node.getChildNodes; % create array of children nodes
+  nChild = Child.getLength;    % number of children
+  
+  % --- pass 1: how many children with each name -----------------------
+  f = [];
+  for iChild = 1:nChild        % read in each child
+    [cname cLeaf] = NodeName(Child.item(iChild-1), Pref.KeepNS);
+    if (cLeaf<0), continue; end % unsupported leaf node types
+    if (~isfield(f,cname)),
+      f.(cname)=0;           % initialize first time I see this name
+    end
+    f.(cname) = f.(cname)+1; % add to the counter
+  end                        % end for iChild
+  % text_nodes become CONTENT & for some reason current xmlread 'creates' a
+  % lot of empty text fields so f.CONTENT value should not be trusted
+  if (isfield(f,'CONTENT') && f.CONTENT>2), f.CONTENT=2; end
+  
+  % --- pass 2: store all the children as struct of cell arrays ----------
+  for iChild = 1:nChild        % read in each child
+    [c cname cLeaf] = DOMnode2struct(Child.item(iChild-1), Pref, level+1);
+    if (cLeaf && isempty(c))   % if empty leaf node than skip
+      continue;                % usually empty text node or one of unhandled node types
+    elseif (nChild==1 && cLeaf==1)
+      s=c;                     % shortcut for a common case
+    else                       % if normal node
+      if (level>Pref.NumLevels), continue; end
+      n = f.(cname);           % how many of them in the array so far?
+      if (~isfield(s,cname))   % encountered this name for the first time
+        if (n==1)              % if there will be only one of them ...
+          s.(cname) = c;       % than save it in format it came in
+        else                   % if there will be many of them ...
+          s.(cname) = cell(1,n);
+          s.(cname){1} = c;    % than save as cell array
+        end
+        f.(cname) = 1;         % initialize the counter
+      else                     % already have seen this name
+        s.(cname){n+1} = c;    % add to the array
+        f.(cname) = n+1;       % add to the array counter
+      end
+    end
+  end   % for iChild
+end % end if (node.hasChildNodes)
+
+%% === Step 4: Post-process struct's created for nodes with children =====
+if (isstruct(s))
+  fields = fieldnames(s);
+  nField = length(fields);
+  
+  % Detect structure that looks like Html table and store it in cell Matrix
+  if (nField==1 && strcmpi(fields{1},Pref.TableName{1}))
+    tr = s.(Pref.TableName{1});
+    fields2 = fieldnames(tr{1});
+    if (length(fields2)==1 && strcmpi(fields2{1},Pref.TableName{2}))
+      % This seems to be a special structure such that for 
+      % Pref.TableName = {'tr','td'} 's' corresponds to 
+      %    <tr> <td>M11</td> <td>M12</td> </tr>
+      %    <tr> <td>M12</td> <td>M22</td> </tr>
+      % Recognize it as encoding for 2D struct
+      nr = length(tr);
+      for r = 1:nr
+        row = tr{r}.(Pref.TableName{2});
+        Table(r,1:length(row)) = row; %#ok<AGROW>
+      end
+      s = Table;
+    end
+  end
+
+  % --- Post-processing: convert 'struct of cell-arrays' to 'array of structs'
+  % Example: let say s has 3 fields s.a, s.b & s.c  and each field is an
+  % cell-array with more than one cell-element and all 3 have the same length.
+  % Then change it to array of structs, each with single cell.
+  % This way element s.a{1} will be now accessed through s(1).a
+  vec = zeros(size(fields));
+  for i=1:nField, vec(i) = f.(fields{i}); end
+  if (numel(vec)>1 && vec(1)>1 && var(vec)==0)  % convert from struct of
+    s = cell2struct(struct2cell(s), fields, 1); % arrays to array of struct
+  end % if anyone knows better way to do above conversion please let me know.
+
+end
+
+%% === Step 5: Process nodes with attributes =============================
+if (node.hasAttributes && Pref.ReadAttr)
+  if (~isstruct(s)),              % make into struct if is not already
+    ss.CONTENT=s;
+    s=ss;
+  end
+  Attr  = node.getAttributes;     % list of all attributes
+  for iAttr = 1:Attr.getLength    % for each attribute
+    name  = char(Attr.item(iAttr-1).getName);  % attribute name
+    name  = str2varName(name, Pref.KeepNS);    % fix name if needed
+    value = char(Attr.item(iAttr-1).getValue); % attribute value
+    value = str2var(value, Pref.Str2Num, 1);   % convert to number if possible
+    s.ATTRIBUTE.(name) = value;   % save again
+  end                             % end iAttr loop
+end % done with attributes
+if (~isstruct(s)), return; end %The rest of the code deals with struct's
+
+%% === Post-processing: fields of "s"
+% convert  'cell-array of structs' to 'arrays of structs'
+fields = fieldnames(s);     % get field names
+nField = length(fields);
+for iItem=1:length(s)       % for each struct in the array - usually one
+  for iField=1:length(fields)
+    field = fields{iField}; % get field name
+    % if this is an 'item' field and user want to leave those as cells
+    % than skip this one
+    if (strcmpi(field, Pref.ItemName) && Pref.CellItem), continue; end
+    x = s(iItem).(field);
+    if (iscell(x) && all(cellfun(@isstruct,x(:))) && numel(x)>1) % it's cell-array of structs
+      % numel(x)>1 check is to keep 1 cell-arrays created when Pref.CellItem=1
+      try                           % this operation fails sometimes
+        % example: change s(1).a{1}.b='jack'; s(1).a{2}.b='john'; to
+        % more convinient s(1).a(1).b='jack'; s(1).a(2).b='john';
+        s(iItem).(field) = [x{:}]';  %#ok<AGROW> % converted to arrays of structs
+      catch %#ok<CTCH>
+        % above operation will fail if s(1).a{1} and s(1).a{2} have
+        % different fields. If desired, function forceCell2Struct can force
+        % them to the same field structure by adding empty fields.
+        if (Pref.NoCells)
+          s(iItem).(field) = forceCell2Struct(x); %#ok<AGROW>
+        end
+      end % end catch
+    end
+  end
+end
+
+%% === Step 4: Post-process struct's created for nodes with children =====
+
+% --- Post-processing: remove special 'item' tags ---------------------
+% many xml writes (including xml_write) use a special keyword to mark
+% arrays of nodes (see xml_write for examples). The code below converts
+% s.item to s.CONTENT
+ItemContent = false;
+if (isfield(s,Pref.ItemName))
+  s.CONTENT = s.(Pref.ItemName);
+  s = rmfield(s,Pref.ItemName);
+  ItemContent = Pref.CellItem; % if CellItem than keep s.CONTENT as cells
+end
+
+% --- Post-processing: clean up CONTENT tags ---------------------
+% if s.CONTENT is a cell-array with empty elements at the end than trim
+% the length of this cell-array. Also if s.CONTENT is the only field than
+% remove .CONTENT part and store it as s.
+if (isfield(s,'CONTENT'))
+  if (iscell(s.CONTENT) && isvector(s.CONTENT))
+    x = s.CONTENT;
+    for i=numel(x):-1:1, if ~isempty(x{i}), break; end; end
+    if (i==1 && ~ItemContent)
+      s.CONTENT = x{1};   % delete cell structure
+    else
+      s.CONTENT = x(1:i); % delete empty cells
+    end
+  end
+  if (nField==1)
+    if (ItemContent)
+      ss = s.CONTENT;       % only child: remove a level but ensure output is a cell-array
+      s=[]; s{1}=ss;
+    else
+      s = s.CONTENT;        % only child: remove a level
+    end
+  end
+end
+
+
+
+%% =======================================================================
+%  === forceCell2Struct Function =========================================
+%  =======================================================================
+function s = forceCell2Struct(x)
+% Convert cell-array of structs, where not all of structs have the same
+% fields, to a single array of structs
+
+%% Convert 1D cell array of structs to 2D cell array, where each row
+% represents item in original array and each column corresponds to a unique
+% field name. Array "AllFields" store fieldnames for each column
+AllFields = fieldnames(x{1});     % get field names of the first struct
+CellMat = cell(length(x), length(AllFields));
+for iItem=1:length(x)
+  fields = fieldnames(x{iItem});  % get field names of the next struct
+  for iField=1:length(fields)     % inspect all fieldnames and find those
+    field = fields{iField};       % get field name
+    col = find(strcmp(field,AllFields),1);
+    if isempty(col)               % no column for such fieldname yet
+      AllFields = [AllFields; field]; %#ok<AGROW>
+      col = length(AllFields);    % create a new column for it
+    end
+    CellMat{iItem,col} = x{iItem}.(field); % store rearanged data
+  end
+end
+%% Convert 2D cell array to array of structs
+s = cell2struct(CellMat, AllFields, 2);
+
+%% =======================================================================
+%  === str2var Function ==================================================
+%  =======================================================================
+function val=str2var(str, option, attribute)
+% Can this string 'str' be converted to a number? if so than do it.
+val = str;
+len = numel(str);
+if (len==0    || option==0), return; end % Str2Num="never" of empty string -> do not do enything
+if (len>10000 && option==1), return; end % Str2Num="smart" and string is very long -> probably base64 encoded binary
+digits = '(Inf)|(NaN)|(pi)|[\t\n\d\+\-\*\.ei EI\[\]\;\,]';
+s = regexprep(str, digits, ''); % remove all the digits and other allowed characters
+if (~all(~isempty(s)))          % if nothing left than this is probably a number
+  if (~isempty(strfind(str, ' '))), option=2; end %if str has white-spaces assume by default that it is not a date string
+  if (~isempty(strfind(str, '['))), option=2; end % same with brackets
+  str(strfind(str, '\n')) = ';';% parse data tables into 2D arrays, if any
+  if (option==1)                % the 'smart' option
+    try                         % try to convert to a date, like 2007-12-05
+      datenum(str);             % if successful than leave it as string
+    catch                       %#ok<CTCH> % if this is not a date than ...
+      option=2;                 % ... try converting to a number
+    end
+  end
+  if (option==2)
+    if (attribute)
+      num = str2double(str);      % try converting to a single number using sscanf function
+      if isnan(num), return; end  % So, it wasn't really a number after all    
+    else
+      num = str2num(str);         %#ok<ST2NM> % try converting to a single number or array using eval function
+    end
+    if(isnumeric(num) && numel(num)>0), val=num; end % if convertion to a single was succesful than save
+  end
+elseif ((str(1)=='[' && str(end)==']') || (str(1)=='{' && str(end)=='}')) % this looks like a (cell) array encoded as a string
+  try 
+    val = eval(str); 
+  catch              %#ok<CTCH>
+    val = str; 
+  end                     
+elseif (~attribute)   % see if it is a boolean array with no [] brackets
+  str1 = lower(str);
+  str1 = strrep(str1, 'false', '0');
+  str1 = strrep(str1, 'true' , '1');
+  s = regexprep(str1, '[01 \;\,]', ''); % remove all 0/1, spaces, commas and semicolons 
+  if (~all(~isempty(s)))          % if nothing left than this is probably a boolean array
+    num  = str2num(str1); %#ok<ST2NM>
+    if(isnumeric(num) && numel(num)>0), val = (num>0);  end % if convertion was succesful than save as logical
+  end
+end
+
+
+%% =======================================================================
+%  === str2varName Function ==============================================
+%  =======================================================================
+function str = str2varName(str, KeepNS)
+% convert a sting to a valid matlab variable name
+if(KeepNS)
+  str = regexprep(str,':','_COLON_', 'once', 'ignorecase');
+else
+  k = strfind(str,':');
+  if (~isempty(k))
+    str = str(k+1:end);
+  end
+end
+str = regexprep(str,'-','_DASH_'  ,'once', 'ignorecase');
+if (~isvarname(str)) && (~iskeyword(str))
+  str = genvarname(str);
+end
+
+%% =======================================================================
+%  === NodeName Function =================================================
+%  =======================================================================
+function [Name LeafNode] = NodeName(node, KeepNS)
+% get node name and make sure it is a valid variable name in Matlab.
+% also get node type:
+%   LeafNode=0 - normal element node,
+%   LeafNode=1 - text node
+%   LeafNode=2 - supported non-text leaf node,
+%   LeafNode=3 - supported processing instructions leaf node,
+%   LeafNode=-1 - unsupported non-text leaf node
+switch (node.getNodeType)
+  case node.ELEMENT_NODE
+    Name = char(node.getNodeName);% capture name of the node
+    Name = str2varName(Name, KeepNS);     % if Name is not a good variable name - fix it
+    LeafNode = 0;
+  case node.TEXT_NODE
+    Name = 'CONTENT';
+    LeafNode = 1;
+  case node.COMMENT_NODE
+    Name = 'COMMENT';
+    LeafNode = 2;
+  case node.CDATA_SECTION_NODE
+    Name = 'CDATA_SECTION';
+    LeafNode = 2;
+  case node.DOCUMENT_TYPE_NODE
+    Name = 'DOCUMENT_TYPE';
+    LeafNode = 2;
+  case node.PROCESSING_INSTRUCTION_NODE
+    Name = 'PROCESSING_INSTRUCTION';
+    LeafNode = 3;
+  otherwise
+    NodeType = {'ELEMENT','ATTRIBUTE','TEXT','CDATA_SECTION', ...
+      'ENTITY_REFERENCE', 'ENTITY', 'PROCESSING_INSTRUCTION', 'COMMENT',...
+      'DOCUMENT', 'DOCUMENT_TYPE', 'DOCUMENT_FRAGMENT', 'NOTATION'};
+    Name = char(node.getNodeName);% capture name of the node
+    warning('xml_io_tools:read:unkNode', ...
+      'Unknown node type encountered: %s_NODE (%s)', NodeType{node.getNodeType}, Name);
+    LeafNode = -1;
+end
+
+
diff --git a/lib/vendor/nemo2profilelib/+utils/xml_write.m b/lib/vendor/nemo2profilelib/+utils/xml_write.m
new file mode 100644
index 0000000000000000000000000000000000000000..10a18deac7bffe1df8bc8a74867471f69dfde794
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/+utils/xml_write.m
@@ -0,0 +1,447 @@
+function DOMnode = xml_write(filename, tree, RootName, Pref)
+%XML_WRITE  Writes Matlab data structures to XML file
+%
+% DESCRIPTION
+% xml_write( filename, tree) Converts Matlab data structure 'tree' containing
+% cells, structs, numbers and strings to Document Object Model (DOM) node
+% tree, then saves it to XML file 'filename' using Matlab's xmlwrite
+% function. Optionally one can also use alternative version of xmlwrite
+% function which directly calls JAVA functions for XML writing without
+% MATLAB middleware. This function is provided as a patch to existing
+% bugs in xmlwrite (in R2006b).
+%
+% xml_write(filename, tree, RootName, Pref) allows you to specify
+% additional preferences about file format
+%
+% DOMnode = xml_write([], tree) same as above except that DOM node is
+% not saved to the file but returned.
+%
+% INPUT
+%   filename     file name
+%   tree         Matlab structure tree to store in xml file.
+%   RootName     String with XML tag name used for root (top level) node
+%                Optionally it can be a string cell array storing: Name of
+%                root node, document "Processing Instructions" data and
+%                document "comment" string
+%   Pref         Other preferences:
+%     Pref.ItemName - default 'item' -  name of a special tag used to
+%                     itemize cell or struct arrays
+%     Pref.XmlEngine - let you choose the XML engine. Currently default is
+%       'Xerces', which is using directly the apache xerces java file.
+%       Other option is 'Matlab' which uses MATLAB's xmlwrite and its
+%       XMLUtils java file. Both options create identical results except in
+%       case of CDATA sections where xmlwrite fails.
+%     Pref.CellItem - default 'true' - allow cell arrays to use 'item'
+%       notation. See below.
+%    Pref.RootOnly - default true - output variable 'tree' corresponds to
+%       xml file root element, otherwise it correspond to the whole file.
+%     Pref.StructItem - default 'true' - allow arrays of structs to use
+%       'item' notation. For example "Pref.StructItem = true" gives:
+%         <a>
+%           <b>
+%             <item> ... <\item>
+%             <item> ... <\item>
+%           <\b>
+%         <\a>
+%       while "Pref.StructItem = false" gives:
+%         <a>
+%           <b> ... <\b>
+%           <b> ... <\b>
+%         <\a>
+%
+%
+% Several special xml node types can be created if special tags are used
+% for field names of 'tree' nodes:
+%  - node.CONTENT - stores data section of the node if other fields
+%    (usually ATTRIBUTE are present. Usually data section is stored
+%    directly in 'node'.
+%  - node.ATTRIBUTE.name - stores node's attribute called 'name'.
+%  - node.COMMENT - create comment child node from the string. For global
+%    comments see "RootName" input variable.
+%  - node.PROCESSING_INSTRUCTIONS - create "processing instruction" child
+%    node from the string. For global "processing instructions" see
+%    "RootName" input variable.
+%  - node.CDATA_SECTION - stores node's CDATA section (string). Only works
+%    if Pref.XmlEngine='Xerces'. For more info, see comments of F_xmlwrite.
+%  - other special node types like: document fragment nodes, document type
+%    nodes, entity nodes and notation nodes are not being handled by
+%    'xml_write' at the moment.
+%
+% OUTPUT
+%   DOMnode      Document Object Model (DOM) node tree in the format
+%                required as input to xmlwrite. (optional)
+%
+% EXAMPLES:
+%   MyTree=[];
+%   MyTree.MyNumber = 13;
+%   MyTree.MyString = 'Hello World';
+%   xml_write('test.xml', MyTree);
+%   type('test.xml')
+%   %See also xml_tutorial.m
+%
+% See also
+%   xml_read, xmlread, xmlwrite
+%
+% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com
+
+%% Check Matlab Version
+v = ver('MATLAB');
+v = str2double(regexp(v.Version, '\d.\d','match','once'));
+if (v<7)
+  error('Your MATLAB version is too old. You need version 7.0 or newer.');
+end
+
+%% default preferences
+DPref.TableName  = {'tr','td'}; % name of a special tags used to itemize 2D cell arrays
+DPref.ItemName   = 'item'; % name of a special tag used to itemize 1D cell arrays
+DPref.StructItem = true;  % allow arrays of structs to use 'item' notation
+DPref.CellItem   = true;  % allow cell arrays to use 'item' notation
+DPref.StructTable= 'Html';
+DPref.CellTable  = 'Html';
+DPref.XmlEngine  = 'Matlab';  % use matlab provided XMLUtils
+%DPref.XmlEngine  = 'Xerces';  % use Xerces xml generator directly
+DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings?
+RootOnly         = true;  % Input is root node only
+GlobalProcInst = [];
+GlobalComment  = [];
+GlobalDocType  = [];
+
+%% read user preferences
+if (nargin>3)
+  if (isfield(Pref, 'TableName' )),  DPref.TableName  = Pref.TableName; end
+  if (isfield(Pref, 'ItemName'  )), DPref.ItemName   = Pref.ItemName;   end
+  if (isfield(Pref, 'StructItem')), DPref.StructItem = Pref.StructItem; end
+  if (isfield(Pref, 'CellItem'  )), DPref.CellItem   = Pref.CellItem;   end
+  if (isfield(Pref, 'CellTable')),   DPref.CellTable  = Pref.CellTable; end
+  if (isfield(Pref, 'StructTable')), DPref.StructTable= Pref.StructTable; end
+  if (isfield(Pref, 'XmlEngine' )), DPref.XmlEngine  = Pref.XmlEngine;  end
+  if (isfield(Pref, 'RootOnly'  )), RootOnly         = Pref.RootOnly;   end
+  if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end
+end
+if (nargin<3 || isempty(RootName)), RootName=inputname(2); end
+if (isempty(RootName)), RootName='ROOT'; end
+if (iscell(RootName)) % RootName also stores global text node data
+  rName = RootName;
+  RootName = char(rName{1});
+  if (length(rName)>1), GlobalProcInst = char(rName{2}); end
+  if (length(rName)>2), GlobalComment  = char(rName{3}); end
+  if (length(rName)>3), GlobalDocType  = char(rName{4}); end
+end
+if(~RootOnly && isstruct(tree))  % if struct than deal with each field separatly
+  fields = fieldnames(tree);
+  for i=1:length(fields)
+    field = fields{i};
+    x = tree(1).(field);
+    if (strcmp(field, 'COMMENT'))
+      GlobalComment = x;
+    elseif (strcmp(field, 'PROCESSING_INSTRUCTION'))
+      GlobalProcInst = x;
+    elseif (strcmp(field, 'DOCUMENT_TYPE'))
+      GlobalDocType = x;
+    else
+      RootName = field;
+      t = x;
+    end
+  end
+  tree = t;
+end
+
+%% Initialize jave object that will store xml data structure
+RootName = varName2str(RootName);
+if (~isempty(GlobalDocType))
+  %   n = strfind(GlobalDocType, ' ');
+  %   if (~isempty(n))
+  %     dtype = com.mathworks.xml.XMLUtils.createDocumentType(GlobalDocType);
+  %   end
+  %   DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName, dtype);
+  warning('xml_io_tools:write:docType', ...
+    'DOCUMENT_TYPE node was encountered which is not supported yet. Ignoring.');
+end
+DOMnode = com.mathworks.xml.XMLUtils.createDocument(RootName);
+
+
+%% Use recursive function to convert matlab data structure to XML
+root = DOMnode.getDocumentElement;
+struct2DOMnode(DOMnode, root, tree, DPref.ItemName, DPref);
+
+%% Remove the only child of the root node
+root   = DOMnode.getDocumentElement;
+Child  = root.getChildNodes; % create array of children nodes
+nChild = Child.getLength;    % number of children
+if (nChild==1)
+  node = root.removeChild(root.getFirstChild);
+  while(node.hasChildNodes)
+    root.appendChild(node.removeChild(node.getFirstChild));
+  end
+  while(node.hasAttributes)            % copy all attributes
+    root.setAttributeNode(node.removeAttributeNode(node.getAttributes.item(0)));
+  end
+end
+
+%% Save exotic Global nodes
+if (~isempty(GlobalComment))
+  DOMnode.insertBefore(DOMnode.createComment(GlobalComment), DOMnode.getFirstChild());
+end
+if (~isempty(GlobalProcInst))
+  n = strfind(GlobalProcInst, ' ');
+  if (~isempty(n))
+    proc = DOMnode.createProcessingInstruction(GlobalProcInst(1:(n(1)-1)),...
+      GlobalProcInst((n(1)+1):end));
+    DOMnode.insertBefore(proc, DOMnode.getFirstChild());
+  end
+end
+% Not supported yet as the code below does not work
+% if (~isempty(GlobalDocType))
+%   n = strfind(GlobalDocType, ' ');
+%   if (~isempty(n))
+%     dtype = DOMnode.createDocumentType(GlobalDocType);
+%     DOMnode.insertBefore(dtype, DOMnode.getFirstChild());
+%   end
+% end
+
+%% save java DOM tree to XML file
+if (~isempty(filename))
+  if (strcmpi(DPref.XmlEngine, 'Xerces'))
+    xmlwrite_xerces(filename, DOMnode);
+  else
+    xmlwrite(filename, DOMnode);
+  end
+end
+
+
+%% =======================================================================
+%  === struct2DOMnode Function ===========================================
+%  =======================================================================
+function [] = struct2DOMnode(xml, parent, s, TagName, Pref)
+% struct2DOMnode is a recursive function that converts matlab's structs to
+% DOM nodes.
+% INPUTS:
+%  xml - jave object that will store xml data structure
+%  parent - parent DOM Element
+%  s - Matlab data structure to save
+%  TagName - name to be used in xml tags describing 's'
+%  Pref - preferenced
+% OUTPUT:
+%  parent - modified 'parent'
+
+% perform some conversions
+if (ischar(s) && min(size(s))>1) % if 2D array of characters
+  s=cellstr(s);                  % than convert to cell array
+end
+% if (strcmp(TagName, 'CONTENT'))
+%   while (iscell(s) && length(s)==1), s = s{1}; end % unwrap cell arrays of length 1
+% end
+TagName  = varName2str(TagName);
+
+%% == node is a 2D cell array ==
+% convert to some other format prior to further processing
+nDim = nnz(size(s)>1);  % is it a scalar, vector, 2D array, 3D cube, etc?
+if (iscell(s) && nDim==2 && strcmpi(Pref.CellTable, 'Matlab'))
+  s = var2str(s, Pref.PreserveSpace);
+end
+if (nDim==2 && (iscell  (s) && strcmpi(Pref.CellTable,   'Vector')) || ...
+               (isstruct(s) && strcmpi(Pref.StructTable, 'Vector')))
+  s = s(:);
+end
+if (nDim>2), s = s(:); end % can not handle this case well
+nItem = numel(s);
+nDim  = nnz(size(s)>1);  % is it a scalar, vector, 2D array, 3D cube, etc?
+
+%% == node is a cell ==
+if (iscell(s)) % if this is a cell or cell array
+  if ((nDim==2 && strcmpi(Pref.CellTable,'Html')) || (nDim< 2 && Pref.CellItem))
+    % if 2D array of cells than can use HTML-like notation or if 1D array
+    % than can use item notation
+    if (strcmp(TagName, 'CONTENT')) % CONTENT nodes already have <TagName> ... </TagName>
+      array2DOMnode(xml, parent, s, Pref.ItemName, Pref ); % recursive call
+    else
+      node = xml.createElement(TagName);   % <TagName> ... </TagName>
+      array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call
+      parent.appendChild(node);
+    end
+  else % use  <TagName>...<\TagName> <TagName>...<\TagName> notation
+    array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call
+  end
+%% == node is a struct ==
+elseif (isstruct(s))  % if struct than deal with each field separatly
+  if ((nDim==2 && strcmpi(Pref.StructTable,'Html')) || (nItem>1 && Pref.StructItem))
+    % if 2D array of structs than can use HTML-like notation or
+    % if 1D array of structs than can use 'items' notation
+    node = xml.createElement(TagName);
+    array2DOMnode(xml, node, s, Pref.ItemName, Pref ); % recursive call
+    parent.appendChild(node);
+  elseif (nItem>1) % use  <TagName>...<\TagName> <TagName>...<\TagName> notation
+    array2DOMnode(xml, parent, s, TagName, Pref ); % recursive call
+  else % otherwise save each struct separatelly
+    fields = fieldnames(s);
+    node = xml.createElement(TagName);
+    for i=1:length(fields) % add field by field to the node
+      field = fields{i};
+      x = s.(field);
+      switch field
+        case {'COMMENT', 'CDATA_SECTION', 'PROCESSING_INSTRUCTION'}
+          if iscellstr(x)  % cell array of strings -> add them one by one
+            array2DOMnode(xml, node, x(:), field, Pref ); % recursive call will modify 'node'
+          elseif ischar(x) % single string -> add it
+            struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node'
+          else % not a string - Ignore
+            warning('xml_io_tools:write:badSpecialNode', ...
+             ['Struct field named ',field,' encountered which was not a string. Ignoring.']);
+          end
+        case 'ATTRIBUTE' % set attributes of the node
+          if (isempty(x)), continue; end
+          if (isstruct(x))
+            attName = fieldnames(x);       % get names of all the attributes
+            for k=1:length(attName)        % attach them to the node
+              att = xml.createAttribute(varName2str(attName(k)));
+              att.setValue(var2str(x.(attName{k}),Pref.PreserveSpace));
+              node.setAttributeNode(att);
+            end
+          else
+            warning('xml_io_tools:write:badAttribute', ...
+              'Struct field named ATTRIBUTE encountered which was not a struct. Ignoring.');
+          end
+        otherwise                            % set children of the node
+          struct2DOMnode(xml, node, x, field, Pref ); % recursive call will modify 'node'
+      end
+    end  % end for i=1:nFields
+    parent.appendChild(node);
+  end
+%% == node is a leaf node ==
+else  % if not a struct and not a cell than it is a leaf node
+  switch TagName % different processing depending on desired type of the node
+    case 'COMMENT'   % create comment node
+      com = xml.createComment(s);
+      parent.appendChild(com);
+    case 'CDATA_SECTION' % create CDATA Section
+      cdt = xml.createCDATASection(s);
+      parent.appendChild(cdt);
+    case 'PROCESSING_INSTRUCTION' % set attributes of the node
+      OK = false;
+      if (ischar(s))
+        n = strfind(s, ' ');
+        if (~isempty(n))
+          proc = xml.createProcessingInstruction(s(1:(n(1)-1)),s((n(1)+1):end));
+          parent.insertBefore(proc, parent.getFirstChild());
+          OK = true;
+        end
+      end
+      if (~OK)
+        warning('xml_io_tools:write:badProcInst', ...
+          ['Struct field named PROCESSING_INSTRUCTION need to be',...
+          ' a string, for example: xml-stylesheet type="text/css" ', ...
+          'href="myStyleSheet.css". Ignoring.']);
+      end
+    case 'CONTENT' % this is text part of already existing node
+      txt  = xml.createTextNode(var2str(s, Pref.PreserveSpace)); % convert to text
+      parent.appendChild(txt);
+    otherwise      % I guess it is a regular text leaf node
+      txt  = xml.createTextNode(var2str(s, Pref.PreserveSpace));
+      node = xml.createElement(TagName);
+      node.appendChild(txt);
+      parent.appendChild(node);
+  end
+end % of struct2DOMnode function
+
+%% =======================================================================
+%  === array2DOMnode Function ============================================
+%  =======================================================================
+function [] = array2DOMnode(xml, parent, s, TagName, Pref)
+% Deal with 1D and 2D arrays of cell or struct. Will modify 'parent'.
+nDim = nnz(size(s)>1);  % is it a scalar, vector, 2D array, 3D cube, etc?
+switch nDim
+  case 2 % 2D array
+    for r=1:size(s,1)
+      subnode = xml.createElement(Pref.TableName{1});
+      for c=1:size(s,2)
+        v = s(r,c);
+        if iscell(v), v = v{1}; end
+        struct2DOMnode(xml, subnode, v, Pref.TableName{2}, Pref ); % recursive call
+      end
+      parent.appendChild(subnode);
+    end
+  case 1 %1D array
+    for iItem=1:numel(s)
+      v = s(iItem);
+      if iscell(v), v = v{1}; end
+      struct2DOMnode(xml, parent, v, TagName, Pref ); % recursive call
+    end
+  case 0 % scalar -> this case should never be called
+    if ~isempty(s) 
+      if iscell(s), s = s{1}; end
+      struct2DOMnode(xml, parent, s, TagName, Pref );
+    end
+end
+
+%% =======================================================================
+%  === var2str Function ==================================================
+%  =======================================================================
+function str = var2str(object, PreserveSpace)
+% convert matlab variables to a string
+switch (1)
+  case isempty(object)
+    str = '';
+  case (isnumeric(object) || islogical(object))
+    if ndims(object)>2, object=object(:); end  % can't handle arrays with dimention > 2
+    str=mat2str(object);           % convert matrix to a string
+    % mark logical scalars with [] (logical arrays already have them) so the xml_read
+    % recognizes them as MATLAB objects instead of strings. Same with sparse
+    % matrices
+    if ((islogical(object) && isscalar(object)) || issparse(object)),
+      str = ['[' str ']'];
+    end
+    if (isinteger(object)),
+      str = ['[', class(object), '(', str ')]'];
+    end
+  case iscell(object)
+    if ndims(object)>2, object=object(:); end  % can't handle cell arrays with dimention > 2
+    [nr nc] = size(object);
+    obj2 = object;
+    for i=1:length(object(:))
+      str = var2str(object{i}, PreserveSpace);
+      if (ischar(object{i})), object{i} = ['''' object{i} '''']; else object{i}=str; end
+      obj2{i} = [object{i} ','];
+    end
+    for r = 1:nr, obj2{r,nc} = [object{r,nc} ';']; end
+    obj2 = obj2.';
+    str = ['{' obj2{:} '}'];
+  case isstruct(object)
+    str='';
+    warning('xml_io_tools:write:var2str', ...
+      'Struct was encountered where string was expected. Ignoring.');
+  case isa(object, 'function_handle')
+    str = ['[@' char(object) ']'];
+  case ischar(object)
+    str = object;
+  otherwise
+    str = char(object);
+end
+
+%% string clean-up
+str=str(:); str=str.';            % make sure this is a row vector of char's
+if (~isempty(str))
+  str(str<32|str==127)=' ';       % convert no-printable characters to spaces
+  if (~PreserveSpace)
+    str = strtrim(str);             % remove spaces from begining and the end
+    str = regexprep(str,'\s+',' '); % remove multiple spaces
+  end
+end
+
+%% =======================================================================
+%  === var2Namestr Function ==============================================
+%  =======================================================================
+function str = varName2str(str)
+% convert matlab variable names to a sting
+str = char(str);
+p   = strfind(str,'0x');
+if (~isempty(p))
+  for i=1:length(p)
+    before = str( p(i)+(0:3) );          % string to replace
+    after  = char(hex2dec(before(3:4))); % string to replace with
+    str = regexprep(str,before,after, 'once', 'ignorecase');
+    p=p-3; % since 4 characters were replaced with one - compensate
+  end
+end
+str = regexprep(str,'_COLON_',':', 'once', 'ignorecase');
+str = regexprep(str,'_DASH_' ,'-', 'once', 'ignorecase');
+
diff --git a/lib/vendor/nemo2profilelib/exclude_elements.m b/lib/vendor/nemo2profilelib/exclude_elements.m
new file mode 100644
index 0000000000000000000000000000000000000000..d3f56bc37026a7cdb65413f0ebeaac82b7087dd4
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/exclude_elements.m
@@ -0,0 +1,38 @@
+function [ resulting_list ] = exclude_elements( p_source, p_exclude_items, p_source_type )
+%EXCLUDE_ELEMENTS Excludes items from given source.
+%   Runs through the given source and excludes all given elements from it.
+%   Currently only source type 'dir' is implemented.
+%
+%   Parameters:
+%   p_source            Input source, class depends on p_source_type.
+%   p_exclude_items     Items to exclude from p_source.
+%   p_source_type       Type of given source, currently only 'dir' is implemented.
+
+%% Initialize return variables
+resulting_list = false;
+
+%% Parameter check
+if nargin ~= 3
+    warning([mfilename ': Parameter not set correctly! Returning false.']);
+    return
+end
+
+%% Switch source type
+
+switch p_source_type
+    case 'dir'
+        tmp_list = [];
+        for o_index = 1:length(p_source) % check if element is a directory
+            if ~any(strcmp(p_source(o_index).name, p_exclude_items))
+                tmp_list = [tmp_list p_source(o_index)];
+            end % ignore every other elements
+        end
+        resulting_list = tmp_list; % copy the created list to the correct variable
+        clear tmp_list o_index; % clean up
+    otherwise
+        warning([mfilename ': Unknown source type! Returning false.']);
+        return
+end
+
+end
+
diff --git a/lib/vendor/nemo2profilelib/exclude_files.m b/lib/vendor/nemo2profilelib/exclude_files.m
new file mode 100644
index 0000000000000000000000000000000000000000..30992de450bae0befb664396117c7df9574e6753
--- /dev/null
+++ b/lib/vendor/nemo2profilelib/exclude_files.m
@@ -0,0 +1,30 @@
+function [ folder_list ] = exclude_files( p_source )
+%EXCLUDE_FILES Excludes all files from a list of folders given by p_source.
+%   Runs through p_source and excludes all files that are found.
+%
+%   Parameters:
+%   p_source            List of directories.
+
+%% Initialize return variables
+folder_list = false;
+
+%% Parameter check
+if ~isstruct(p_source)
+    warning([mfilename ': Parameter not set correctly! Returning false.']);
+    return
+end
+
+%% Exclude all files
+
+tmp_list = [];
+for o_index = 1:size(p_source, 1) % check if element is a directory
+    %if isdir(p_source(o_index).name)
+    if p_source(o_index).isdir
+        tmp_list = [tmp_list p_source(o_index)];
+    end % ignore every other elements
+end
+folder_list = tmp_list; % copy the created list to the correct variable
+clear tmp_list o_index; % clean up
+
+end
+