% This script creates data objects and data type objects in the base
% workspace. It configures the code generation settings (properties) of the
% objects based on the specifications in an ICD.
%
% To import the specifications from the ICD, you can use the 'xlsread' or
% 'readtable' functions. When you use 'readtable', MATLAB stores the
% imported data in a table, which can result in cleaner and more readable
% MATLAB code.
%% Configure Object Properties by Using Imported Data
% Some of the column names in the ICD, such as 'DataType' in the 'Signals'
% worksheet, match the names of properties of data objects and data type
% objects in MATLAB. Some of the cell values in the ICD workbook are
% compatible with the property values in MATLAB, for example the value of
% the 'DataType' column for the signal 'sensors', which is 'Bus:
% EngSensors'. After you import these compatible cell values from the ICD,
% you can assign the values directly to the object properties.
%
% Suppose you store the name of a property as a character vector in the
% variable 'propName' and you store the property value in the variable
% 'propValue'. Use this command to set the property value:
% myDataObject.(propName) = propValue; 
% You can iteratively
% (for example, with a 'for' loop) assign property values to data objects.
%
% Other column names in the ICD do not directly match property names, for
% example 'HeaderFile' in the 'Signals' worksheet (to assign 'HeaderFile'
% to a data object, you must use the nested property
% 'myDataObject.CoderInfo.CustomAttributes.HeaderFile'). Some cell values
% in the ICD are not compatible with property values in MATLAB, for example
% the 'Sheet' formula in the 'Parameters' worksheet. For these property
% names and cell values, use 'if' and 'switch case' statements in MATLAB to
% conditionally set the property values.
%
% To execute a command or function on each of multiple objects or variables
% whose names you store in a cell array of character vectors, use the
% function 'eval' in a 'for' loop. For example, if you store the names
% of several variables in the cell array of character vectors
% 'objectNames', use this command in a 'for' loop with index 'i':
% eval([objectNames{i} '.(propName) = propValue;'])
%% Designate Name of External File
xlsFileName = 'ex_ICD_PCG.xls';
%% Import Numeric Types
% In this part of the script, use the function 'readtable' to import the raw information
% from the ICD. The imported information appears in a table. Each column of
% the ICD, such as 'WordLength', appears as a variable in the table.
%
% The value of each variable in the table is a numeric array or a cell
% array of character vectors, depending on the values contained in the
% corresponding column of the ICD. To access the numeric data, use
% parentheses, (). To access the character vectors in the cells, use
% brackets, {}.

numTypesRaw = readtable(xlsFileName,'Sheet','Numeric Types');

objectNames = numTypesRaw.Name; % "Name" is a variable in the table "numTypesRaw".

% Create a list of the variables (columns) in the table.
tableVariables = numTypesRaw.Properties.VariableNames;

% Loop through the rows (objects) in the imported data.
for row = 1:length(objectNames)
    
    objectName = objectNames{row};
    
    % If the object does not exist, create it. Use Simulink.NumericType
    % objects to represent most data types.
    if ~exist(objectName)
        eval([objectName ' = Simulink.NumericType;'])
    end
    
    % Loop through the columns (properties) in the imported data.
    for col = 1:length(tableVariables)
        
        propName = tableVariables{col};
        
        % Store the property value in the variable 'propValue'. Depending
        % on whether 'readtable' stored the information as a numeric array
        % or cell array, use parentheses or brackets to access the value.
        if iscell(numTypesRaw.(propName))
            propValue = numTypesRaw.(propName){row};
        else
            propValue = numTypesRaw.(propName)(row);
        end
        
        % Assign the value to the property in the object.
        if eval(['isprop(' objectName ',''' propName ''')']) && ~strcmp(propValue,'N/A')
            eval([objectName '.(propName) = propValue;'])
        end
    end
    
    % These 'if' statements handle special cases where the column name or
    % cell value from the ICD is not directly compatible with an object
    % property in MATLAB.
    if ~strcmp(numTypesRaw.Sign{row},'N/A')
        if strcmp(numTypesRaw.Sign{row},'yes')
            eval([objectName '.Signedness = ''Signed'';'])
        else
            eval([objectName '.Signedness = ''Unsigned'';'])
        end
    end
    if ~strcmp(numTypesRaw.HeaderFile{row},'N/A')
        eval([objectName '.HeaderFile = numTypesRaw.HeaderFile{row};'])
    end
    
end
%% Import Structure Types
% This part of the script uses the function 'xlsread' to import the
% information. The imported information, including the column names,
% appears in a cell matrix of character vectors. To access the information
% in the cells, use brackets, {}.

[~, ~, structTypesRaw] = xlsread(xlsFileName,'Structure Types','');

objectNames = structTypesRaw(2:end,1);
propNames = structTypesRaw(1,2:end)';
typeHeaderFiles = {};

% Shave off the first column and the first row, which no longer contain
% useful information.
structTypesRaw = structTypesRaw(2:end,2:end);

for row = 1:length(objectNames)
    
    objectName = objectNames{row};
    
    % If data type is 'struct', create a Simulink.Bus object.
    % Use Simulink.Bus objects to represent structure types in Simulink.
    DTValue = structTypesRaw{row,strcmp(propNames,'DataType')};
    if strcmp(DTValue,'struct')
        if ~exist(objectName)
            eval([objectName ' = Simulink.Bus;'])
        end
        
        % Get a list of the elements (Simulink.BusElement objects) in the
        % bus object. Each element corresponds to a field of the structure
        % type that the bus object represents.
        busName = objectName;
        eval(['elementNames = {' busName '.Elements.Name};'])
        
        for col = 1:length(propNames)
            propName = propNames{col};
            propValue = structTypesRaw{row,col};
            if eval(['isprop(' objectName ',''' propName ''')']) && ...
                    ~strcmp(propName,'DataType') && ~strcmp(propValue,'N/A')
                eval([objectName '.(propName) = propValue;'])
            end
        end
        
        % If the structure type is imported, begin collecting the names of
        % header files that define imported data types.
        if eval(['strcmp(' busName '.DataScope,''Imported'')'])
            eval(['headerFile = ' busName '.HeaderFile;']);
            if isempty(typeHeaderFiles)
                typeHeaderFiles = {headerFile};
            else
                typeHeaderFiles = [typeHeaderFiles {headerFile}];
            end
        end
        
        % If data type is not 'struct', create an element
        % (Simulink.BusElement object) for the latest bus object. If the
        % bus object already has the element, replace the element.
    else
        
        % Determine whether the latest bus object has the element.
        elementIndex = eval(['strcmp(elementNames,''' objectName ''')']);
        if isempty(elementIndex) || ~any(elementIndex)
            eval([objectName ' = Simulink.BusElement;'])
        else
            % If the latest bus object has the element, to preserve the
            % property values of this existing element, copy the element
            % into a separate variable in the base workspace.
            eval([objectName ' = ' busName '.Elements(elementIndex);'])
        end
        
        eval([objectName '.Name = objectName;'])
        
        for col = 1:length(propNames)
            propName = propNames{col};
            propValue = structTypesRaw{row,col};
            if eval(['isprop(' objectName ',''' propName ''')']) && ~strcmp(propValue,'N/A')
                eval([objectName '.(propName) = propValue;'])
            end
        end
        
        % Add the Simulink.BusElement object as an element to the latest
        % bus object.
        if any(elementIndex)
            eval([busName '.Elements(elementIndex) = ' objectName ';'])
        else
            eval([busName '.Elements = [' busName '.Elements; ' objectName '];'])
        end
        
        eval(['clear ' objectName])
        
    end
    
end
%% Import Enumerated Types
% This part of the scripts creates enumerated type definitions by using the function
% 'Simulink.defineIntEnumType'.

enumTypesRaw = readtable(xlsFileName,'Sheet','Enumerated Types');

objectNames = enumTypesRaw.Name;

for row = 1:length(objectNames)
    
    % If the value in the StorageType column is not 'member', begin a new
    % enumerated type definition.
    if ~strcmp(enumTypesRaw.StorageType{row},'member')
        
        enumName = objectNames{row};
        
        % The value in the StorageType column can be a specific integer
        % data type, such as 'int8', or it can be generic 'int'.
        if strcmp(enumTypesRaw.StorageType{row},'int')
            storageTypeChar = '';
        else
            storageTypeChar = [',''StorageType'',''' enumTypesRaw.StorageType{row} ''''];
        end
        
        if strcmp(enumTypesRaw.DataScope{row},'Exported')
            dataScopeChar = ',''DataScope'',''Exported''';
        else
            dataScopeChar = ',''DataScope'',''Imported''';
            
            % If the enumerated type is imported, add the name of the
            % header file to the list of custom header files that define
            % imported types.
            if isempty(typeHeaderFiles)
                typeHeaderFiles = {enumTypesRaw.HeaderFile{row}};
            else
                typeHeaderFiles = [typeHeaderFiles {enumTypesRaw.HeaderFile{row}}];
            end
        end
        
        if strcmp(enumTypesRaw.HeaderFile{row},'N/A')
            headerFileChar = '';
        else
            headerFileChar = [',''HeaderFile'',''' enumTypesRaw.HeaderFile{row} ''''];
        end
        
        enumMembers = {};
        enumIntValues = [];
        subRow = row+1;
        
        % Begin collecting enumeration members from the ICD. Stop
        % collecting enumeration members when the row represents a
        % different enumerated type.
        while subRow <= length(enumTypesRaw.Name) && strcmp(enumTypesRaw.StorageType{subRow},'member')
            
            if isempty(enumMembers)
                enumMembers = {enumTypesRaw.Name{subRow}};
            else
                enumMembers = [enumMembers enumTypesRaw.Name{subRow}];
            end
            
            if isempty(enumIntValues)
                enumIntValues = str2double(enumTypesRaw.UnderlyingInt{subRow});
            else
                enumIntValues = [enumIntValues str2double(enumTypesRaw.UnderlyingInt{subRow})];
            end
            
            subRow = subRow+1;
            
        end
        
        % Use the function 'Simulink.defineIntEnumType' to define the
        % enumerated type for use during this MATLAB session.
        %
        % If the enumerated type already exists, so that the script cannot
        % redefine it, explain that the user must close models and clear
        % variables.
        try
            eval(['Simulink.defineIntEnumType(enumName,enumMembers,enumIntValues' storageTypeChar dataScopeChar headerFileChar ')'])
        catch
            eval(['sprintf(''Cannot redefine enumerated type ' enumName ' because open models and existing variables use the type. Close the models and clear the variables.'')'])
        end
        
    end
end
%% Import Signals
[~, ~, sigsRaw] = xlsread(xlsFileName,'Signals','');

objectNames = sigsRaw(2:end,1);
propNames = sigsRaw(1,2:end)';

sigsRaw = sigsRaw(2:end,2:end);

for row = 1:length(objectNames)
    
    objectName = objectNames{row};
    
    if ~exist(objectName)
        eval([objectName ' = Simulink.Signal;'])
    end
    
    % Prepare to use custom storage classes.
    eval([objectName '.CoderInfo.StorageClass = ''Custom'';'])
    
    for col = 1:length(propNames)
        propName = propNames{col};
        propValue = sigsRaw{row,col};
        
        % This switch statement handles special cases.
        switch propName
            case 'Owner'
                % Each Simulink.Signal object can use a custom storage
                % class, which controls the representation of the data item
                % in the generated code. For data items that
                % sldemo_fuelsys_dd_controller owns, use the custom storage
                % class ExportToFile. For data items that the component
                % does not own, use ImportFromFile.
                if strcmp(propValue,'sldemo_fuelsys_dd_controller')
                    eval([objectName '.CoderInfo.CustomStorageClass = ''ExportToFile'';'])
                    eval([objectName '.CoderInfo.CustomAttributes.Owner = propValue;'])
                else
                    eval([objectName '.CoderInfo.CustomStorageClass = ''ImportFromFile'';'])
                end
            case 'HeaderFile'
                if ~strcmp(propValue','N/A')
                    eval([objectName '.CoderInfo.CustomAttributes.HeaderFile = propValue;'])
                end
            case 'DefinitionFile'
                if ~strcmp(propValue,'N/A')
                    if eval(['strcmp(' objectName '.CoderInfo.CustomStorageClass,''ExportToFile'')'])
                        eval([objectName '.CoderInfo.CustomAttributes.DefinitionFile = propValue;'])
                    elseif eval(['strcmp(' objectName '.CoderInfo.CustomStorageClass,''ImportFromFile'')'])
                        % Start a list of custom source files that define
                        % (allocate memory for) data items. Later, you can
                        % identify these source files for inclusion in the
                        % build process.
                        if ~exist('customSourceFiles')
                            customSourceFiles = {propValue};
                        else
                            customSourceFiles = [customSourceFiles propValue];
                        end
                    end
                end
        end
        
        if eval(['isprop(' objectName ',''' propName ''')']) && ~strcmp(propValue,'N/A')
            eval([objectName '.(propName) = propValue;'])
        end
    end
    
end
%% Import Parameters
[~, ~, paramsRaw] = xlsread(xlsFileName,'Parameters','');

objectNames = paramsRaw(2:end,1);
propNames = paramsRaw(1,2:end)';

paramsRaw = paramsRaw(2:end,2:end);

for row = 1:length(objectNames)
    
    objectName = objectNames{row};
    
    if ~exist(objectName)
        eval([objectName '= Simulink.Parameter;'])
    end
    
    eval([objectName '.CoderInfo.StorageClass = ''Custom'';'])
    
    for col = 1:length(propNames)
        propName = propNames{col};
        propValue = paramsRaw{row,col};
        
        switch propName
            case 'Owner'
                if strcmp(propValue,'sldemo_fuelsys_dd_controller')
                    eval([objectName '.CoderInfo.CustomStorageClass = ''ExportToFile'';'])
                    eval([objectName '.CoderInfo.CustomAttributes.Owner = propValue;'])
                else
                    eval([objectName '.CoderInfo.CustomStorageClass = ''ImportFromFile'';'])
                end
            case 'HeaderFile'
                if ~strcmp(propValue','N/A')
                    eval([objectName '.CoderInfo.CustomAttributes.HeaderFile = propValue;'])
                end
            case 'DefinitionFile'
                if ~strcmp(propValue,'N/A')
                    if eval(['strcmp(' objectName '.CoderInfo.CustomStorageClass,''ExportToFile'')'])
                        eval([objectName '.CoderInfo.CustomAttributes.DefinitionFile = propValue;'])
                    elseif eval(['strcmp(' objectName '.CoderInfo.CustomStorageClass,''ImportFromFile'')'])
                        if ~exist('customSourceFiles')
                            customSourceFiles = {propValue};
                        else
                            customSourceFiles = [customSourceFiles propValue];
                        end
                    end
                end
            case 'Value'
                % When the parameter value is stored in a different
                % worksheet in the ICD, use 'xlsread' again to import that
                % value.
                if strcmp(propValue,'Sheet')
                    eval([objectName '.Value = xlsread(xlsFileName,objectName,'''');'])
                end
        end
        
        if eval(['isprop(' objectName ',''' propName ''')']) && ...
                ~strcmp(propValue,'N/A') && ~strcmp(propValue,'Sheet')
            eval([objectName '.(propName) = propValue;'])
        end
    end
    
end
%% Set Up Custom Header Files
% If you import custom data types, to enable the Stateflow chart in the
% model to use the types, add #include statements in the Simulation Target
% header file of the model.
if ~isempty(typeHeaderFiles)
    typeHeaderFiles = unique(typeHeaderFiles);
    headerFileChar = '';
    for i = 1:length(typeHeaderFiles)
        headerFileChar = sprintf([headerFileChar '\n#include "' typeHeaderFiles{i} '"']);
    end
    set_param('sldemo_fuelsys_dd_controller',...
        'SimCustomHeaderCode',headerFileChar)
end
%% Set Up Custom Source Files
% Use the model configuration parameters to identify the custom source
% files that define imported data. When the model compiles the generated
% code, the identified source files are included in the compilation.
if exist('customSourceFiles')
    customSourceFiles = unique(customSourceFiles);
    customSourceFilesList = customSourceFiles{1};
    
    for i = 2:length(customSourceFiles)
        customSourceFilesList = [customSourceFilesList ',' customSourceFiles{i}];
    end
    
    set_param('sldemo_fuelsys_dd_controller','CustomSource',customSourceFilesList)
end