Simulink Bus Object Report Example

This example shows how to create a report describing all bus objects used by a model. This report creates a chapter for each bus object. Each chapter has a section for the bus hierarchy, a used-by block list, bus properties table, and bus elements properties table.

Sample Simulink Bus Object Report.

rptview asbhl20_bus_report.pdf

Create Report

Open a model. If Aerospace Blockset is installed, use asbhl20 example model to get a more comprehensive bus object report.

model = "sldemo_bus_arrays";
open_system(model);

Create and open a report object. Disable the CompileModelBeforeReporting property because compiled information gathered by calling the busobjinfo local function (see below).

rpt = slreportgen.report.Report(model + "_bus_report", "pdf");
rpt.CompileModelBeforeReporting = false;
open(rpt);

Add a titlepage and a table of contents.

titlepage = mlreportgen.report.TitlePage("Title", model + ": Bus Object Report");
add(rpt, titlepage);
toc = mlreportgen.report.TableOfContents();
add(rpt, toc);

Extract bus object information from a model by using the busobjinfo local function (see below). This function returns two values, one for looking up bus objects from a bus name, and the other for looking up user-by blocks from a bus name.

[busMap, usersMap] = busobjinfo(model);

Get all bus names used by model.

busNames = keys(busMap);

Go through all bus objects and create a section for each bus object.

nBus = busMap.Count;
for i = 1:nBus
    busName = busNames{i};

Create bus object section and create a link target so other parts of the report can link.

    busID = createBusLinkID(busName);
    busSection = mlreportgen.report.Section( ...
        "Title", busName, ...
        "LinkTarget", busID);    

Create bus object hierarchy subsection. See the createBusHierarchyList local function (see below) for how to traverse through a bus object hierarchy to create a bus hierarchy list.

    hierarchySection = mlreportgen.report.Section("Hierarchy");
    hierarchyList = createBusHierarchyList(busName, busMap, inf);
    add(hierarchySection, hierarchyList);
    add(busSection, hierarchySection);    

Create used-by section. See the createUsedBySection local function (see below) for how see how to create highlighted system snapshots and a used by list.

    usedBySection = createUsedBySection(busName, usersMap);
    add(busSection, usedBySection);

Create bus object properties table. See the createBusPropertiesTable local function (see below) for how to extract bus object information to create a bus properties table.

    busobjPropertiesSection = mlreportgen.report.Section("Properties");
    propTable = createBusPropertiesTable(busName, busMap);
    add(busobjPropertiesSection, propTable);
    add(busSection, busobjPropertiesSection);

Create bus element properties table. See the createBusElementsTable local function (see below) for how to extract bus element information and create a bus elements table.

    elemsSection = mlreportgen.report.Section("Elements");
    elemsTable = createBusElementsTable(busName, busMap);
    add(elemsSection, elemsTable);
    add(busSection, elemsSection);

Add bus object section to report, and clear highlighted used-by blocks.

    add(rpt, busSection);
    
    % Clear highlighted used-by blocks
    users = usersMap(busName);
    hilite_system(users, 'none');
end    

Close an view report.

close(rpt);
rptview(rpt);   

Create Bus Hierarchy List

The createBusHierarchyList function creates an unordered list describing a bus object hierarchy. Each list item has an internal link to the bus element portion of the report. The inputs to this function are a bus name, a busMap object for resolving bus objects, and a number to determine how far into the hierarchy to traverse to create the list.

function list = createBusHierarchyList(busName, busMap, depth)
    % Create an unordered list.
    list = mlreportgen.dom.UnorderedList();
    
    % Get bus object from name.
    busObj = busMap(busName);
    
    % Go through each bus element and create a linked list item.
    nBusElements = numel(busObj.Elements);
    for i = 1:nBusElements
        busElement = busObj.Elements(i);
        busElementName = busElement.Name;
        
        % Create a list item
        listItem = mlreportgen.dom.ListItem();

        % Call createBusElementLinkID local function to get a link ID.
        busElementID = createBusElementLinkID(busName, busElementName);
        
        % If bus element is a known bus object, then create a sublist. Otherwise,
        % create a list item.
        busElementType = busElement.DataType;
        if isKey(busMap, busElementType)
            % Create a link to the bus element section
            label = compose("%s (%s)", busElementName, busElementType);
            internalLink = mlreportgen.dom.InternalLink(busElementID, label);
            append(listItem, internalLink);
            append(list, listItem);
            
            % Create sublist only if traversal depth is greater than 0.
            if (depth > 0)
                % Create sublist by recursively calling createBusHierarchyList 
                % function.
                sublist = createBusHierarchyList(busElementType, busMap, depth - 1);
                append(list, sublist);
            end
        else
            % Create a link to the bus element section.
            internalLink = mlreportgen.dom.InternalLink(busElementID, busElementName);
            append(listItem, internalLink);
            append(list, listItem);
        end
    end
end

Create Used-By Section

The createUsedBySection function creates a section containing system snapshots with blocks highlighted that are used-by the bus object. The snapshots are followed by a used-by blocks list. The inputs to this function are a bus name and a busMap object for resolving bus objects.

function usedBySection = createUsedBySection(busName, usersMap)
    % Create used-by section
    usedBySection = mlreportgen.report.Section("Used-By");

    % Get used-by list for a bus.    
    allUsers = usersMap(busName);
    
    % Go through each block to determine which system they belong to and group them
    % with their parent system. Results are grouped in a containers.Map object to 
    % allow for quick and easy lookup.
    sysUserMap = containers.Map();
    nUsers = numel(allUsers);
    for i = 1:nUsers
        user = allUsers(i);
        
        % Get parent system.
        sys = get_param(user, "Parent");
        if isKey(sysUserMap, sys)
            % Parent system already exists, append to list.
            sysUserMap(sys) = [sysUserMap(sys) user];
        else
            % Create new system entry.
            sysUserMap(sys) = user;
        end
    end

    % Go through all systems. Note that all entries in a containers.Map are sorted.
    systems = keys(sysUserMap);
    nSys = sysUserMap.Count;
    for i = 1:nSys
        sys = systems{i};
        
        % Get user list for a system.
        sysUsers = sysUserMap(sys);
        
        % Highlight users.
        sysUserHs = get_param(sysUsers, "Handle");
        if iscell(sysUserHs)
            sysUserHs = cell2mat(sysUserHs);
        end
        hilite_system(sysUserHs);
            
        % Create system snapshot and add it to the used-by section.
        diag = slreportgen.report.Diagram(sys);
        add(usedBySection, diag);
        
        % Create unordered list of users.
        ul = mlreportgen.dom.UnorderedList();
        if numel(sysUsers) == 1
            append(ul, {sysUsers});
        else
            append(ul, sysUsers);
        end
        add(usedBySection, ul);
    end
end

Create Bus Properties Table

The createBusPropertiesTable function creates a bus properties table that has two columns. One column is for property names, and the second column is for property values. For the elements property, the createBusPropertiesTable creates a hierarchy list that points to the bus element portion of the report. The inputs to this function are a bus name and a busMap object for resolving bus objects.

function table = createBusPropertiesTable(busName, busMap)
    % Create an empty 2-column table, with solid borders.
    table = mlreportgen.dom.Table(2);
    table.Width = "100%";
    table.Border = "solid";
    table.ColSep = "solid";
    table.RowSep = "solid";
    
    % Set first column to 25% width and set second column to 75% width.
    cols = mlreportgen.dom.TableColSpecGroup();
    cols.Span = 2;
    col1 = mlreportgen.dom.TableColSpec();
    col1.Style = {mlreportgen.dom.Width("25%")};
    col2 = mlreportgen.dom.TableColSpec();
    col2.Style = {mlreportgen.dom.Width("75%")};
    cols.ColSpecs = [col1, col2];
    table.ColSpecGroups = cols;    

    % Get bus object from name.
    busObj = busMap(busName);
    
    % Create a list of bus properties to report on.
    properties = [ ...
        "Elements" ....
        "DataScope" ...
        "HeaderFile" ...
        "Alignment" ...
        "Description" ...
        ];
   
    % Go through bus properties list.
    for property = properties
        % Create table row
        row = mlreportgen.dom.TableRow();
        
        % Create property name table entry.
        nameEntry = mlreportgen.dom.TableEntry(property);
        nameEntry.VAlign = "middle";

        % Create property value table entry.
        value = busObj.(property);
        if isa(value, "Simulink.BusElement")
            % Property value is a bus element. Create a simple bus hierarchy list 
            % by calling createBusHierarchyList with a depth of 0.
            valueDOMObj = createBusHierarchyList(busName, busMap, 0);
        else
            % Convert property value to a string.
            valueString = mlreportgen.utils.toString(value);
            valueDOMObj = mlreportgen.dom.Text(valueString);
        end
        valueEntry = mlreportgen.dom.TableEntry();
        append(valueEntry, valueDOMObj);

        % Add name entry to row.
        append(row, nameEntry);
        
        % Add value entry to row.
        append(row, valueEntry);
        
        % Add row to table.
        append(table, row);
    end
end

Create Bus ElementsTable

The createBusElementsTable function creates a bus elements table that has three columns. The first column has bus element names that spans multiple rows to group together bus element properties. The second column has bus element property names, and the third column has bus element property values. For the bus element datatype property, if the value is a bus object, then the function creates a link to the bus object section. The inputs to this function are a bus name and a busMap object for resolving bus objects.

function table = createBusElementsTable(busName, busMap)
    % Create an empty 3 column table, with solid borders.
    table = mlreportgen.dom.Table();
    table.Width = "100%";
    table.Border = "solid";
    table.ColSep = "solid";
    table.RowSep = "solid";
    
    % Set first column to 25% width, second column to 25% width, and third column to
    % 50% width.
    grps = mlreportgen.dom.TableColSpecGroup();
    grps.Span = 3;
    specs1 = mlreportgen.dom.TableColSpec();
    specs1.Style = {mlreportgen.dom.Width('25%')};
    specs2 = mlreportgen.dom.TableColSpec();
    specs2.Style = {mlreportgen.dom.Width('25%')};
    specs3 = mlreportgen.dom.TableColSpec();
    specs3.Style = {mlreportgen.dom.Width('50%')};
    grps.ColSpecs = [specs1 specs2 specs3];
    table.ColSpecGroups = grps;    

    % Get bus object from name.
    busObj = busMap(busName);
    
    % List of bus element properties to report on.
    properties = [ ...
        "DataType", ...
        "Complexity", ...
        "Dimensions"...
        "DimensionsMode", ...
        "SampleTime", ...
        "Min", ...
        "Max", ...
        "Unit", ...
        "Description", ...
        ];
    nProperties = numel(properties);
    
    % Go through all bus elements.
    busElements = busObj.Elements;
    nElements = numel(busElements);
    for i = 1:nElements
        % Create table row
        row = mlreportgen.dom.TableRow();

        % Get bus element name.
        busElement = busElements(i);
        busElementName = busElement.Name;
        
        % Create bus element link target, so other internal links can point to it.
        busElementID = createBusElementLinkID(busName, busElementName);
        busElementLinkTarget = mlreportgen.dom.LinkTarget(busElementID);
        busElementPara = mlreportgen.dom.Paragraph(busElementName);
        append(busElementPara, busElementLinkTarget);
        
        % Create table entry for first column with element name and target.
        busElementEntry = mlreportgen.dom.TableEntry();
        append(busElementEntry, busElementPara);        

        % Set bus element row to span the number of bus element properties. This
        % groups together bus element properties to the bus element row.
        busElementEntry.RowSpan = nProperties;
        busElementEntry.InnerMargin = "2pt";
        busElementEntry.VAlign = "middle";
        
        % Add bus element name entry to row.
        append(row, busElementEntry);

        % Go through bus element properties
        for property = properties
            
            % First row is not empty because it was created above to hold the bus 
            % element name.
            if isempty(row)
                row = mlreportgen.dom.TableRow();
            end
            
            % Create table entry for bus element property.
            propNameEntry = mlreportgen.dom.TableEntry(property);
            propNameEntry.InnerMargin = "2pt";

            % Get bus element value.
            value = busElement.(property);
            valueString = mlreportgen.utils.toString(value);

            if (strcmp(property, "DataType") && isKey(busMap, valueString))
                % Bus element value is a bus object data type.  Create a link to bus
                % object section.
                propValueEntry = mlreportgen.dom.TableEntry();
                busID = createBusLinkID(valueString);
                busLink = mlreportgen.dom.InternalLink(busID, valueString);
                append(propValueEntry, busLink);
            else
                propValueEntry = mlreportgen.dom.TableEntry(valueString);
            end
            propValueEntry.InnerMargin = "2pt";
            
            % Append bus elment table property name and value entries.
            append(row, propNameEntry);
            append(row, propValueEntry);

            % Append bus element property table row.
            append(table, row);
            
            % Clear row to allow for bus bus element property rows.
            row = [];
        end % bus element properties
    end % bus element
end

Extract Bus Information from a Model

The busobjinfo function finds all bus objects used by a model and returns a busMap and a userMap. The busMap is a containers.Map object that maps bus name to a bus object. The userMap is a containers.Map object that maps a bus name to a sorted list of model, subsystems, and blocks tthat use the bus object.

function [busMap, userMap] = busobjinfo(model)
    % Create containers.Map to store bus object information.  A containers.Map data 
    % type allows for quick lookup of bus object information based on bus names.
    busMap = containers.Map();
    userMap = containers.Map();
    
    % Find all model variables, including model references.
    results = Simulink.findVars(model, "SearchReferencedModels", "on");

    % Get model workspace to resolve variable names.
    modelWorkspace = get_param(model, "ModelWorkspace");

    % Go through all search results.
    nResults = numel(results);
    for i = 1:nResults
        result = results(i);
        name = result.Name;
        source = result.Source;
        
        % Resolve variable value based on source type.
        switch lower(result.SourceType)
            case "base workspace"
                % Variable is defined in base workspace.  Determine variable value by 
                % calling EVALIN.
                value = evalin("base", name);

            case "model workspace"
                % Variable is defined in model workspace.  Determine variable value 
                % from model workspace object.
                value = getVariable(modelWorkspace, "name");

            case "mask workspace"
                % Variable is defined in mask workspace.  Get mask object and find
                % variable to determine value.
                maskObj = Simulink.Mask.get(source);
                vars = getWorkspaceVariables(maskObj);
                idx = strcmp({vars.Name}, name);
                value = vars(idx).Value;
                
            case "data dictionary"
                % Variable is defined in data dictionary. Open data dictionary to 
                % and get variable name entry to determine variable value.
                dict = Simulink.data.dictionary.open(source);
                sect = getSection(dict, "Design Data");
                entry = getEntry(sect, result.Name);
                value = getValue(entry);
        end
        
        % If variable value is a Simulink.Bus, then store results in maps.
        if isa(value, "Simulink.Bus")
            busMap(name) = value;
            userMap(name) = sort(string(result.Users));
        end
    end 
end

Bus and Bus Element Link Identifiers

Linking from one document location to another document location in a document requires a matching string for both the source and destination. This string is called a link identifier. The following two local functions create link identifiers for the bus object and bus elements.

function id = createBusElementLinkID(busName, busElement)
    % Bus element id consists of the bus name and bus element name.  It is hashed to 
    % force the id to be no longer than 40 characters.  An id longer than 40 
    % characters may not work in Microsoft Word documents.
    id = mlreportgen.utils.hash(compose("bus-element-%s-%s", busName, busElement));
end

function id = createBusLinkID(busName)
    % Bus id is the bus name.  It is hashed to force the id to be no longer than 40 
    % characters.  An id longer than 40 characters may not work in Microsoft Word 
    % documents.
    id = mlreportgen.utils.hash(compose("bus-%s", busName));
end