Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

コマンド ライン API を使用して要件エディターで Simulink モデルをドキュメント化

この例では、Simulink 要件エディターで設計をドキュメント化するために、Simulink® および Simulink Requirements® API を使用して Simulink モデル構造体を自動的にキャプチャおよびリンクします。自動化は、リンクされたアーティファクトを置換または変更した後で、要件のトレーサビリティ データを修復または移行するのにも役立ちます。以下のコマンド ライン API の使用が示されます。

  • 新しい要件セットを作成するためのslreq.new

  • 要件セットにエントリを追加するためのslreq.ReqSet

  • 子要件を追加するためのadd

  • Description フィールドを埋めるためのslreq.Requirement

  • SRC から DEST へのリンクを作成するためのslreq.createLink

  • Simulink Requirements オブジェクトを検索するためのslreq.find

  • 既存のリンク先の終端を再接続するためのsetDestination

  • 既存のリンクを新しいソース オブジェクトに移動させるためのsetSource

  • ソース オブジェクトを見つけることができないリンクを特定するためのisResolvedSource

  • 指定された slreq.Link のリンク元またはリンク先いずれかの終端を表示するために使用されるslreq.show

一部の場所では、リタイア済みの SLVnV 製品の要件管理インターフェイス (RMI) の部分から継承した従来のrmiAPI も使用します。

使用例 1: Simulink Requirements における Simulink モデル サロゲートとのリンク

Simulink Requirements 製品を使用して、Simulink 設計の詳細な説明を作成し、Simulink モデルに一致する階層内の要件コレクションを整理する必要があります。また、この要件コレクションの項目と設計内の対応する要素との間を簡単に移動する方法も必要です。

このデモンストレーションを目的として、slvnvdemo_powerwindowController.slx の機能的プロパティを検証するために設計された slvnvdemo_powerwindow_vs.slx 仕様モデルについて考えます。

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

従来の VNV/RMI 製品 API である rmi('getObjectsInModel',MODEL) を使用して MODEL 内のオブジェクトの階層リストを取得してから、Simulink Requirements slreq.* APIs を使用して各 Simulink モデルのサロゲート (表現) を自動的に生成します。

その後、関連する設計要件情報を自動生成されたプロキシ項目の [説明] フィールドまたは [根拠] フィールドに指定することができます。

2 つのモデル サロゲートを使用して 1 つの要件セットを作成するスクリプトを下に示します。下の 3 つのコマンドは、プロキシ項目に関する Description フィールドをプログラムによって埋める方法を示す例を提供していますが、ほとんどの場合、この操作はエディターで対話的に行います。

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'];

モデル オブジェクトとプロキシ項目の間のトレーサビリティの作成

要件エディターで各モデルの構造体を参照できるようになりました。また、[説明] フィールドを編集して、各設計要素に関する追加の詳細情報を指定できるようになりました。足りないのは、Simulink ブロック線図内のオブジェクトと Simulink Requirements のプロキシ/サロゲート項目の間を簡単に移動できる方法です。下のスクリプトは、ナビゲーション リンクを自動的に追加するためのslreq.createLinkAPI の使用方法を示しています。目的のレベルの粒度を選択できます。この例では、SubSystem ブロックへのリンクを制限します。

また、強調表示を有効にして、ナビゲーション リンクを受信した Simulink オブジェクトを可視化することもできます。

Simulink ブロックからのリンクで移動し、Simulink 要件エディターで対応するプロキシ項目を確認します。[詳細] ペインの [リンク] (右下) に、関連するブロックへのハイパーリンクがあることに注意してください。

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

使用例 2: リンク先アーティファクトの置換後に既存のリンクを再利用

設計プロジェクト開発のコースでは、一連の新しい要件に移行しなければならない場合があります。現在の要件にリンクがあり、リンクされた元の要件を新しい要件セットの対応するエントリに関連付けるための既知のルールがある場合、可能であればリンクを自動的に移行して、すべてのリンク設定を手動で再実行することを回避する必要がある場合があります。キーワード、根拠のステートメント、コメント履歴などの既存のメタデータが維持されるため、既存のリンクを移行することは新しいリンクを再作成することよりも推奨されます。

リンクを移行しなければならない場合がある状況例を迅速にまとめるために、まずは Microsoft Word ドキュメントからインポートされた要件から開始し、複数のリンクを作成します。

次に、複数のリンクを対話的 (要件パースペクティブ モードでドラッグアンドドロップ) または API を使用して作成し、Simulink オブジェクトとインポートされた要件の間の移動を可能にします。

Simulink オブジェクトからの移動は、コンテキスト メニューを介して、または要件パースペクティブ モードのときはクリック 1 回で実行されます。

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

インポートされた代替の要件セットに移動するためのリンクの更新

Word ドキュメントを "読み取り専用の参照として" インポートしました。これは、オプションの引数を指定せずに実行した場合のslreq.importコマンドの既定の設定です。このインポート モードでは、より新しいバージョンのソース ドキュメントが使用可能になったときに、インポートされた項目を後で更新することができます。ここで、考えが変わったとします。つまり、インポートした項目を Simulink 要件エディターで完全に編集 (追加、削除、構造的な移動など) できるようにする必要があるとします。"参照として" インポートされた項目のプロパティをロック解除して編集できますが、インポートされた項目を並べ替えたり、新しい項目を追加することはできません。また、項目を削除した場合、変更された外部ドキュメントから次の update を実行したときに再表示されます。制限のない編集機能が必要な場合、別のインポート モードを使用します。つまり、slreq.importコマンドに対して追加の AsReference 引数を提供し、その値として false を指定することで、"編集可能な要件として" 使用します。

これにより新しい要件セットが生成され、Simulink Requirements 内で排他的に管理されます。元の外部ドキュメントとの接続はなく、必要に応じて自由にエントリを追加、移動、削除できます。説明やその他のプロパティを変更するために、インポートされた項目のロックを解除する必要はなくなりました。

ただし、問題があります。以前に作成したリンクは Simulink から元の読み取り専用の参照に接続し、最近インポートされた編集可能な要件には接続しません。この問題を解決するには、新しくインポートされた (編集可能な) セット内の対応する項目に既存のリンクをリダイレクトするスクリプトを作成して実行します。setDestinationAPI を使用して必要な更新を実行します。

LinkSet 内のすべてのリンクをループし、対応する編集可能な項目と接続するように影響を受けるリンクを調整すると、モデル ブロックから移動するときに、編集可能なセット内の適切な項目が開き、両方のブロックからの内向きリンクが表示されます。

このタスクを達成するスクリプト例を以下に示します。

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

使用例 3: ソース オブジェクトの置き換え後に既存の外向きリンクを再利用

要件への多数のリンクをもつサブシステムがあり、このサブシステムを再設計するか完全に置き換える必要がある状況について考えてみます。新しい実装も大部分は同じであり、可能であれば (同じ名前をもつブロックがモデルの構造体階層の同じレベルに存在する場合) 既存のリンクを維持する必要があります。こうすることで、手動でのリンク設定手順を、元の実装に存在していないブロックのみに制限することができます。サブシステムを置き換えた後、setSourceAPI を使用して既存のリンクを新しいソース オブジェクトで再接続します。リンクは一意の永続的な Session Independent Identifier (SID) に依存し、リンク ソース オブジェクト (リンクを "所有する" Simulink オブジェクト) に関連付けられ、置換されたサブシステムには各オブジェクトの新しい SID があるため、古いリンクを単純に使用し続けることはできないことに注意してください。

モデル例を用いてsetSourceAPI の使用方法を示すには、単に前のセクションでリンク設定した 2 つの Truth Table ブロックをまったく同じ新しいブロックで置き換えます。新しい Truth Table のコピーには新しい SID があるため、これが完了したらリンクは unresolved になります。

要件エディターで、[リンクの表示] をクリックすると、切断されたすべてのリンクにオレンジ色の三角形のインジケーターがあることがわかります。インジケーターは合計で 4 つあります。これは、置換された各ブロックに 2 つのリンクがあったからで、1 つは slvnvdemo_powerwindowController.slreqx 内のサロゲート項目へのリンク、もう 1 つは 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

リンク元の終端を更新して切断されたリンクを修復

新しいモデル内のすべてのリンクを反復し、isResolvedSourceAPI を使用してソースが関連付けられていないリンクを特定してから、setSourceコマンドを使用して、切断された各リンクを修正する必要があります。古い SID を使用して必要な新しいリンク元を見つけることはできないため、元のモデルを開いて元のブロックのパスと名前を見つけてから、更新されたモデル内で対応するブロック置換を特定します。

下のスクリプト例を参照してください。このスクリプトを実行すると、修正された 4 つのリンクがレポートされます。Simulink 要件エディターで [リンク] ビューをチェックし、現在はすべてのリンクが関連付けられていて、オレンジ色のアイコン インジケーターがどこにも存在しないことを確認します。

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

クリーンアップ

上記の手順の実行後にクリーンアップするには、すべてのモデルを閉じ、この例の中でスクリプトによって作成されたすべてのファイルを削除します。slreq.clearコマンドは、次に実行する操作との競合を回避するために、すべての要件とリンク データを MATLAB セッション メモリから削除します。

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');