Main Content

Use Command-line API to Document Simulink Model in Requirements Editor

This example uses Simulink® and Simulink Requirements® APIs to automatically capture and link Simulink model structure, for the purpose of documenting the design in Simulink Requirements Editor. Automation will also help to repair or migrate requirements traceability data after replacing or modifying linked artifacts. The use of the following command-line APIs is demonstrated:

  • slreq.new for creating a new Requirement Set

  • slreq.ReqSet for adding entries to a Requirement Set

  • add for adding child requirements

  • slreq.Requirement for filling-in the Description field

  • slreq.createLink for creating link from SRC to DEST

  • slreq.find for locating Simulink Requirements objects

  • setDestination for re-connecting the destination end of an existing link

  • setSource for moving an existing link to the new source object

  • isResolvedSource for identifying links whose source object cannot be found

  • slreq.show used to view either the source or the destination end of a given slreq.Link

In a few places we also use the legacy rmi APIs that are inherited from Requirements Management Interface (RMI) part of the retired SLVnV Product.

USE CASE 1: Link with Simulink Model Surrogate in Simulink Requirements

You want to use the Simulink Requirements product to create a detailed description of your Simulink design, and you want to organize your Requirements collection in a hierarchy that matches your Simulink models. You also want an easy way to navigate between the items of this Requirements collection and the corresponding elements in your design.

For the purpose of this demonstration, consider the slvnvdemo_powerwindow_vs.slx specification model designed for verifying the functional properties of slvnvdemo_powerwindowController.slx.

open_system('slvnvdemo_powerwindow_vs');
open_system('slvnvdemo_powerwindowController');

We use the legacy VNV/RMI product API, rmi('getObjectsInModel',MODEL), to get a hierarchical list of objects in MODEL, then use Simulink Requirements slreq.* APIs to automatically generate the surrogate (representation) for each of our Simulink models.

We can then provide related design requirements information in the Description or Rational fields of auto-generated proxy items.

Below is the script that builds one Requirement Set with two model surrogates. The bottom three commands provide an example of how to programmatically fill-in the Description field for a proxy item, but most probably you will do this interactively in the Editor.

models = {'slvnvdemo_powerwindow_vs', 'slvnvdemo_powerwindowController'};
workDir = tempname;
disp(['Using ' workDir ' to store generated files.']);
Using C:\Users\ahoward\AppData\Local\Temp\tp048cdc5c_b22d_44f6_bb6c_85d54e962505 to store generated files.
mkdir(workDir);
addpath(workDir);
for modelIdx = 1:length(models)
    modelName = models{modelIdx};
    reqSetFile = fullfile(workDir, [modelName '.slreqx']);
    slProxySet = slreq.new(reqSetFile); % create separate ReqSet file with matching name
    open_system(modelName); % will create a proxy item for each object in this Simulink model
    modelNode = slProxySet.add('Id', modelName, 'Summary', [modelName ' Description']);
    [objHs, parentIdx, isSf, SIDs] = rmi('getobjectsInModel', modelName);
    for objIdx = 1:length(objHs)
        if parentIdx(objIdx) < 0 % top-level item is the model itself
            indexedReqs(objIdx) = modelNode; %#ok<SAGROW>
        else
            parentReq = indexedReqs(parentIdx(objIdx));
            if isSf(objIdx)
                sfObj = Simulink.ID.getHandle([modelName SIDs{objIdx}]);
                if isa(sfObj, 'Stateflow.State')
                    name = sf('get', objHs(objIdx), '.name');
                elseif isa(sfObj, 'Stateflow.Transition')
                    name = sf('get', objHs(objIdx), '.labelString');
                else
                    warning('SF object of type %s skipped.', class(sfObj));
                    continue;
                end
                type = strrep(class(sfObj), 'Stateflow.', '');
            else
                name = get_param(objHs(objIdx), 'Name');
                type = get_param(objHs(objIdx), 'BlockType');
            end
            indexedReqs(objIdx) = parentReq.add(...
                'Id', SIDs{objIdx}, 'Summary', [name ' (' type ')']); %#ok<SAGROW>
        end
    end
    slProxySet.save();  % save the autogenerated Requirement Set
end
slreq.editor(); % open editor to view the constructed Requirement Set
slProxySet = slreq.find('type', 'ReqSet', 'Name', 'slvnvdemo_powerwindow_vs');
roItem = slProxySet.find('type', 'Requirement', 'Summary', 'upD (Inport)');  % will...
%  provide Description text for this item
roItem.Description = ['Driver''s UP button should close the window all the way if...' ...
    '  released within 0.5 seconds'];

Create Traceability Between Model Objects and Proxy Items

Now we can browse the structure of each model in the Requirement Editor, and we can edit the Description fields to provide additional details about each design element. What's missing is an easy way to navigate between the objects in Simulink diagrams and the proxy/surrogate items in Simulink Requirements. The script below demonstrates the use of slreq.createLink API to automatically add navigation links. We can choose any desired level of granularity. For the purpose of this example, we will limit linking to SubSystem blocks.

We can also enable highlighting to visualize which Simulink objects received navigation links.

Navigate the link from Simulink block to review the corresponding proxy item in Simulink Requirements Editor. Note the hyperlink to the associated block in the Details pane under Links at the bottom-right.

for modelIdx = 1:length(models)
    modelName = models{modelIdx};
    counter = 0;
    slProxySet = slreq.find('type', 'ReqSet', 'Name', modelName);
    proxyItems = slProxySet.find('type', 'Requirement');
    for reqIdx = 1:numel(proxyItems)
        roItem = proxyItems(reqIdx);
        if contains(roItem.Summary, '(SubSystem)') % || contains(roItem.Summary, '(State)')
            sid = [modelName roItem.Id];
            disp(['    linking ' sid ' ..']);
            srcObj = Simulink.ID.getHandle(sid);
            link = slreq.createLink(srcObj, roItem);
            link.Description = 'slreq proxy item';
            counter = counter + 1;
        end
    end
    disp(['Created ' num2str(counter) ' links for ' modelName]);
    rmi('highlightModel', modelName);
end
    linking slvnvdemo_powerwindow_vs:394 ..
    linking slvnvdemo_powerwindow_vs:394:224 ..
    linking slvnvdemo_powerwindow_vs:394:272 ..
    linking slvnvdemo_powerwindow_vs:394:271 ..
    linking slvnvdemo_powerwindow_vs:394:360 ..
    linking slvnvdemo_powerwindow_vs:397 ..
    linking slvnvdemo_powerwindow_vs:397:107 ..
    linking slvnvdemo_powerwindow_vs:397:300 ..
    linking slvnvdemo_powerwindow_vs:397:108 ..
    linking slvnvdemo_powerwindow_vs:397:285 ..
    linking slvnvdemo_powerwindow_vs:397:307 ..
    linking slvnvdemo_powerwindow_vs:399 ..
    linking slvnvdemo_powerwindow_vs:399:650 ..
    linking slvnvdemo_powerwindow_vs:399:214 ..
    linking slvnvdemo_powerwindow_vs:399:218 ..
    linking slvnvdemo_powerwindow_vs:399:273 ..
    linking slvnvdemo_powerwindow_vs:160 ..
    linking slvnvdemo_powerwindow_vs:160:643 ..
    linking slvnvdemo_powerwindow_vs:160:646 ..
    linking slvnvdemo_powerwindow_vs:160:590 ..
    linking slvnvdemo_powerwindow_vs:160:591 ..
    linking slvnvdemo_powerwindow_vs:160:648 ..
    linking slvnvdemo_powerwindow_vs:160:592 ..
Created 23 links for slvnvdemo_powerwindow_vs
    linking slvnvdemo_powerwindowController:39 ..
    linking slvnvdemo_powerwindowController:40 ..
    linking slvnvdemo_powerwindowController:36 ..
Created 3 links for slvnvdemo_powerwindowController

USE CASE 2: Reuse Existing Links After Replacing Linked Destination Artifact

In the course of design project development, there may be need to migrate to a new set of Requirements. If current Requirements have links, and when there is a known rule to associate original linked requirements with the corresponding entries in the new Requirement Set, you may want to automatically migrate the links where possible, to avoid redoing all the linking manually. Migrating existing links is preferred over re-creating new links, because you keep the existing metadata such as keywords, rationale statements, comment history.

To quickly put together an example situation where you may need to migrate links, we will start with the Requirements imported from a Microsoft Word document, then create some links.

We then create some links, either interactively (by drag-and-drop in Requirements Perspective mode) or using the API, to allow navigation between Simulink objects and imported requirements.

Navigation from Simulink object is done either via the context menu or with one click when in Requirements Perspective mode.

examplesFolder = fullfile(matlabroot, 'toolbox', 'slrequirements', 'slrequirementsdemos');
docsFolder = fullfile(examplesFolder, 'powerwin_reqs');
addpath(docsFolder); % just in case
externalDocName = 'PowerWindowSpecification';
externalDoc = fullfile(docsFolder, [externalDocName '.docx']);
outputFile = fullfile(workDir, 'ReadOnlyImport.slreqx');
[~,~,roReqSet] = slreq.import(externalDoc, 'ReqSet', outputFile);  % without extra...
% arguments the default import mode is "read-only references"
roReqSet.save();
roItem = roReqSet.find('CustomId', 'Simulink_requirement_item_2');
designModel = 'slvnvdemo_powerwindowController';
link1 = slreq.createLink([designModel '/Truth Table'], roItem);   % link to read-only item...
% in imported set
link2 = slreq.createLink([designModel '/Truth Table1'], roItem);  % link 2nd block to the...
% same read-only item
slreq.show(link1.source());         % highlight te source of 1st link
slreq.show(link1.destination());    % navigate to the target of 1st link
rmi('view', [designModel '/Truth Table1'], 2);  % navigate 2nd link from Truth Table1...
% using legacy RMI('view') API

Updating Links for Navigation to Alternative Imported Requirement Set

Note that we imported our Word document "as read-only references", which is the default for slreq.import command when run without optional arguments. This import mode allows to later update the imported items when the newer version of the source document becomes available. Now, supposed that we changed our mind: we want our imported items to be fully editable in Simulink Requirements Editor, including additions, deletions, and structural moves. Although you can Unlock and edit properties of items imported "as references", you cannot reorder the imported items or add new ones, and if you delete an item, it will re-appear when performing the next update from the modified external document. When unrestricted editing capability is needed, we use a different import mode: "as editable requirements", by providing an additional AsReference argument for the slreq.import command, and specifying false as the value.

This generates a new Requirement Set, to be managed exclusively in Simulink Requirements. There is no connection with the original external document, and you are free to add/move/delete entries as needed. Now, you do not need to Unlock imported items to modify the Description or other properties:

However, there is problem: our previously created links connect from Simulink to the original read-only References, not to the more recently imported editable Requirements. The solution is to create and run a script that redirects the existing links to corresponding items in the newly imported (editable) set. We use setDestination API to perform the required updates.

After we loop over all links in the LinkSet, and adjust the affected links to connect with corresponding editable items, when we navigate from the model block, the correct item in editable set opens, and incoming links from both blocks are shown.

Below is the example script that accomplishes this task.

outputFile = fullfile(workDir, 'EditableImport.slreqx');
% re-import as Editable Requirements
[~,~,mwReqSet] = slreq.import(externalDoc, 'ReqSet', outputFile, 'AsReference', false);
mwReqSet.save();
linkSet = slreq.find('type', 'LinkSet', 'Name', designModel);  % LinkSet for our design model
links = linkSet.getLinks();  % all outgoing links in this LinkSet
updateCount = 0;
for linkIdx = 1:numel(links)
    link = links(linkIdx);
    if strcmp(link.destination.reqSet, [roReqSet.Name '.slreqx']) % if this link points to...
       % an item in read-only ReqSet
        sid = link.destination.sid;          % internal identifier of linked read-only item
        roItem = mwReqSet.find('SID', sid);  % located the linked read-only item
        id = roItem.Id;    % document-side identifier of imported read-only item
        mwItem = mwReqSet.find('Id', id);    % located a matching item in Editable...
       % Requirement Set
        link.setDestination(mwItem);
        updateCount = updateCount + 1;
    end
end
disp(['Updated ' num2str(updateCount) ' links from ' designModel]);
Updated 2 links from slvnvdemo_powerwindowController
slreq.show(link.destination());                  % check updated destination of the last...
% link we modified
rmi('view', [designModel '/Truth Table1'], 2);   % navigate again (legacy API), editable...
% item selected in RE

USE CASE 3: Reuse Existing Outgoing Links After Replacing Source Objects

Consider the situation when you have a SubSystem with lots of links to Requirements, and this subsystem needed to be redesigned or entirely replaced. The new implementation is mostly similar, and you would like to preserve the existing links where possible (where blocks with the same name exists in the same level of the model structure hierarchy). This will allow to limit manual linking steps to only the blocks that did not exist in the original implementation. You use setSource API to re-attach the existing links at new source objects after replacing the SubSystem. Note that you cannot simply keep using the old links, because links rely on unique persistent session-independent identifiers (SIDs) to associate the link source object (the Simulink object that "owns" the link), and your replacement SubSystem has new SIDs for each object.

To demonstrate the use of setSource API with our example model, we will simply replace two Truth Table blocks that we linked in the previous section with same exact new blocks. Once this is done, the links become unresolved, because new Truth Table copies have new SIDs.

In the Requirements Editor, click Show Links and notice the orange triangle indicators for all the broken links. There is a total of 4, because each of the replaced blocks had 2 links: one link to the surrogate item in slvnvdemo_powerwindowController.slreqx and another link to an imported requirement in ReadOnlyImport.slreqx.

slreq.open('slvnvdemo_powerwindowController.slreqx')
ans = 
  ReqSet with properties:

             Description: ''
                    Name: 'slvnvdemo_powerwindowController'
                Filename: 'C:\Users\ahoward\AppData\Local\Temp\tp048cdc5c_b22d_44f6_bb6c_85d54e962505\slvnvdemo_powerwindowController.slreqx'
                Revision: 1
                   Dirty: 0
    CustomAttributeNames: {}
               CreatedBy: 'ahoward'
               CreatedOn: 14-Jan-2021 15:22:27
              ModifiedBy: 'ahoward'
              ModifiedOn: 14-Jan-2021 15:22:27

originalModel = 'slvnvdemo_powerwindowController';
updatedModel = 'UpdatedModel';
save_system(originalModel, fullfile(workDir, [updatedModel '.slx']));  % this also creates...
% .slmx file in workDir
delete_line(updatedModel, 'Mux1/1', 'Truth Table/1');      % disconnect original block
delete_line(updatedModel, 'Truth Table/1', 'control/3');   % disconnect original block
add_block([updatedModel '/Truth Table'], [updatedModel '/New Truth Table']);  % create...
% replacement block
delete_block([updatedModel '/Truth Table']);               % delete original block
add_line(updatedModel, 'Mux1/1', 'New Truth Table/1');     % reconnect new block
add_line(updatedModel, 'New Truth Table/1', 'control/3');  % reconnect new block
set_param([updatedModel '/New Truth Table'], 'Name', 'Truth Table');  % restore original name
delete_line(updatedModel, 'Mux4/1', 'Truth Table1/1');     % disconnect original block
delete_line(updatedModel, 'Truth Table1/1', 'control/4');  % disconnect original block
add_block([updatedModel '/Truth Table1'], [updatedModel '/New Truth Table1']);  % create...
% replacement block
delete_block([updatedModel '/Truth Table1']);              % delete original block
add_line(updatedModel, 'Mux4/1', 'New Truth Table1/1');    % reconnect new block
add_line(updatedModel, 'New Truth Table1/1', 'control/4'); % reconnect new block
set_param([updatedModel '/New Truth Table1'], 'Name', 'Truth Table1');  % restore original name

Update Source Ends to Repair Broken Links

Now we need to iterate over all the links in the new model, identify the ones with unresolved source using isResolvedSource API, then use setSource command to fix each broken link. Because we cannot rely on the old SIDs to find the needed new sources of the link, we open the original model to discover the original block's path and name, then locate the corresponding replacement block in the updated model.

See the example script below. When you run this script, it reports 4 links fixed. Check the Links View in Simulink Requirements Editor and confirm that all the links are now resolved, there are no orange icon indicators anywhere.

open_system(originalModel);
updatedLinkSet = slreq.find('type', 'LinkSet', 'Name', updatedModel);
links = updatedLinkSet.getLinks();
fixCount = 0;
for linkIdx = 1:numel(links)
    link = links(linkIdx);
    if ~link.isResolvedSource()
        missingSID = link.source.id;
        origBlockHandle = Simulink.ID.getHandle([originalModel missingSID]);
        origBlockPath = getfullname(origBlockHandle);
        [~,blockPath] = strtok(origBlockPath, '/');
        updatedBlockPath = [updatedModel blockPath];
        updatedModelSID = Simulink.ID.getSID(updatedBlockPath);
        updatedBlockHandle = Simulink.ID.getHandle(updatedModelSID);
        link.setSource(updatedBlockHandle);
        fixCount = fixCount + 1;
    end
end
updatedLinkSet.save();
disp(['Fixed ' num2str(fixCount) ' links in ' updatedModel '.slmx']);
Fixed 4 links in UpdatedModel.slmx

Cleanup

To cleanup after performing the above steps, we close all models and remove all files that were created by scripts in this example. slreq.clear command will remove all Requirements and Links data from MATLAB session memory, so as to avoid conflicting with what you do next.

slreq.clear();
bdclose('all');
rmpath(workDir);
rmpath(docsFolder);
rmdir(workDir,'s');
 % clear stored import location for our document to avoid prompt on rerun
slreq.import.docToReqSetMap(externalDoc,'clear');