Используйте Command-line API для документирования модели Simulink в редакторе требований

Этот пример использует Simulink ® и Simulink Requirements ® APIs, чтобы автоматически захватывать и связывать структуру модели Simulink с целью документирования проекта в Simulink Requirements Editor. Автоматизация также поможет восстановить или перенести данную трассируемость требований после замены или изменения связанных программных продуктов. Показано использование следующих API командной строки:

  • slreq.new для создания нового набора требований

  • slreq.ReqSet для добавления записей в набор требований

  • add для добавления дочерних требований

  • slreq.Requirement для заполнения Description область

  • slreq.createLink для создания ссылки из SRC на DEST

  • slreq.find для определения местоположения объектов Simulink Requirements

  • setDestination для повторного соединения конца назначения существующей ссылки

  • setSource для перемещения существующей ссылки на новый исходный объект

  • isResolvedSource для идентификации ссылок, исходный объект которых не может быть найден

  • slreq.show используется для просмотра исходного или целевого конца заданного slreq.Link

В нескольких местах мы также используем наследие rmi API, унаследованные от Requirements Management Interface (RMI) продукта SLVnV.

USE CASE 1: Ссылка на Simulink Model Surrogate в Simulink Requirements

Вы хотите использовать продукт Simulink Requirements, чтобы создать подробное описание вашего проекта Simulink, и вы хотите организовать набор Requirements в иерархии, которая соответствует вашим моделям Simulink. Кроме того, требуется простой способ перехода между элементами этого набора Requirements и соответствующими элементами в проекте.

Для целей этой демонстрации рассмотрим slvnvdemo_powerwindow_vs.slx модель спецификации, предназначенная для проверки функциональных свойств slvnvdemo_powerwindowController.slx.

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

Мы используем устаревший API продукта VNV/RMI, rmi('getObjectsInModel',MODEL), чтобы получить иерархический список объектов в MODEL, затем используйте Simulink Requirements slreq.* APIs автоматически сгенерировать суррогат (представление) для каждой из наших моделей Simulink.

Затем мы можем предоставить соответствующую информацию о проекте требованиях в полях Описание (Description) или Рациональное (Rational) автоматически сгенерированных прокси- элементов.

Ниже представлен скрипт, который создает один набор требований с двумя суррогатами модели. Три нижних команды обеспечивают пример того, как программно заполнить 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.createLink API для автоматического добавления ссылок навигации. Мы можем выбрать любой желаемый уровень зернистости. Для целей этого примера мы ограничим привязку к блокам SubSystem.

Мы также можем включить подсветку, чтобы визуализировать, какие объекты Simulink получили навигационные ссылки.

Перейдите по ссылке из блока Simulink, чтобы просмотреть соответствующий элемент прокси в редакторе Simulink Requirements. Обратите внимание на гиперссылку на связанный блок на панели «Сведения» в разделе « Ссылках» внизу справа.

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: Повторно используйте существующие ссылки после замены связанного программного продукта назначения

В ходе разработки проектного проекта может возникнуть необходимость перехода к новому набору Требований. Если текущие Требования имеют ссылки, и когда существует известное правило для связи исходных связанных требований с соответствующими записями в новом Наборе требований, можно захотеть автоматически перенести связи, где это возможно, чтобы избежать повторного связывания вручную. Миграция существующих ссылок предпочтительнее повторного создания новых ссылок, потому что вы сохраняете существующие метаданные, такие как ключевые слова, операторы обоснования, история комментариев.

Чтобы быстро собрать пример ситуации, когда вам может потребоваться миграция ссылок, начнем с требований, импортированных из документа Microsoft Word, а затем создадим несколько ссылок.

Затем мы создадим некоторые ссылки, либо в интерактивном режиме (перетаскиванием в режиме Requirements Perspective), либо с помощью API, чтобы разрешить навигацию между объектами Simulink и импортированными требованиями.

Навигация из объекта Simulink осуществляется либо через контекстное меню, либо одним нажатием кнопки в режиме перспективы требований.

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 Requirements Editor, включая сложения, удаления и структурные перемещения. Несмотря на то, что вы можете разблокировать и редактировать свойства элементов, импортированных «как ссылки», вы не можете переупорядочить импортированные элементы или добавить новые таковые, и если вы удаляете элемент, он снова появится при выполнении следующего update из измененного внешнего документа. Когда необходима неограниченная возможность редактирования, мы используем другой режим импорта: «как редактируемые требования», предоставляя дополнительную AsReference аргумент для slreq.import команда и определение false как значение.

Это создает новый набор требований, которым будут управлять исключительно в Simulink Requirements. Связь с исходным внешним документом отсутствует, и вы можете добавлять/перемещать/удалять записи по мере необходимости. Теперь вам не нужно разблокировать импортированные элементы, чтобы изменить Описание или другие свойства:

Однако есть проблема: наши ранее созданные ссылки соединяются с Simulink на исходные ссылки только для чтения, а не с недавно импортированными редактируемыми Требованиями. Решение состоит в том, чтобы создать и запустить скрипт, который перенаправляет существующие ссылки на соответствующие элементы в только что импортированном (редактируемом) наборе. Используем setDestination API для выполнения необходимых обновлений.

После того, как мы закольцовываем все ссылки в 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

USE CASE 3: Повторно используйте существующие исходящие ссылки после замены исходных объектов

Примите во внимание ситуацию, когда у вас есть Подсистема с множеством ссылок на Требования, и эта подсистема должна быть переработана или полностью заменена. Новая реализация в основном аналогична, и вы хотели бы сохранить существующие ссылки там, где это возможно (где блоки с таким же именем существуют на том же уровне иерархии структуры модели). Это позволит ограничить шаги связывания вручную только те блоки, которые не существовали в исходной реализации. Вы используете setSource API для повторного присоединения существующих ссылок к новым исходным объектам после замены Подсистемы. Обратите внимание, что вы не можете просто продолжать использовать старые ссылки, потому что ссылки полагаются на уникальные постоянные независимые от сеанса идентификаторы (SID), чтобы связать исходный объект ссылки (объект Simulink, который «владеет» соединением), и ваша заменяющая Подсистема имеет новые SID для каждого объекта.

Чтобы продемонстрировать использование setSource API с нашей моделью примеров, мы просто заменим два блока Таблица, которые мы связали в предыдущем разделе, такими же точными новыми блоками. Как только это сделано, ссылки становятся unresolved, поскольку новые копии таблицы истинности имеют новые SID.

В редакторе требований щелкните Показать связи (Show Links) и заметьте оранжевые указатели треугольника для всех разорванных ссылок. Всего 4, потому что каждый из замененных блоков имел 2 ссылки: одна ссылка на суррогатный элемент в slvnvdemo_powerwindowController.slreqx и другая ссылка на импортированное требование в 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

Обновление исходного кода заканчивается для восстановления сломанных ссылок

Теперь нам нужно провести итерацию по всем звеньям в новой модели, идентифицировать таковые с неразрешенным источником с помощью isResolvedSource API, затем используйте setSource команда для исправления каждого разрыва ссылки. Поскольку мы не можем полагаться на старые SID, чтобы найти необходимые новые источники ссылки, мы открываем исходную модель, чтобы обнаружить путь и имя исходного блока, затем найдем соответствующий блок замены в обновленной модели.

См. пример скрипта ниже. Когда вы запускаете этот скрипт, он сообщает о 4 исправленных ссылках. Проверьте Представление Ссылок в Редакторе Simulink Requirements и подтвердите, что все ссылки теперь разрешены, оранжевых значков нет нигде.

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 команда удалит все данные Requirements и ссылки из сеанса работы с 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');
Для просмотра документации необходимо авторизоваться на сайте