Используйте API командной строки, чтобы зарегистрировать модель Simulink в редакторе требований

Этот пример использует Simulink® и Simulink Requirements® APIs, чтобы автоматически получить и соединить структуру модели Simulink, в целях документирования проекта в Редакторе Simulink Requirements. Автоматизация также поможет восстановить или переместить данные о трассируемости требований после заменяющий или изменяющий соединенные артефакты. Использование следующих 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 на пенсии.

ВАРИАНТ ИСПОЛЬЗОВАНИЯ 1: соединитесь с суррогатом модели Simulink в Simulink Requirements

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

В целях этой демонстрации рассмотрите 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'];

Создайте трассируемость между объектами модели и проксируйте элементы

Теперь мы можем просмотреть структуру каждой модели в Редакторе Требования, и мы можем отредактировать поля Description, чтобы предоставить дополнительную подробную информацию о каждом элементе дизайна. То, что отсутствует, является простым способом перейти между объектами в схемах 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

ВАРИАНТ ИСПОЛЬЗОВАНИЯ 2: повторное использование существующие ссылки после заменяющий соединенный целевой артефакт

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

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

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

Навигация от объекта Simulink сделана или через контекстное меню или одним нажатием кнопки когда в режиме Requirements Perspective.

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

ВАРИАНТ ИСПОЛЬЗОВАНИЯ 3: повторное использование существующие исходящие ссылки после заменяющий исходные объекты

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

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

В Редакторе Требований нажмите 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 команда, чтобы зафиксировать каждую неработающую ссылку. Поскольку мы не можем использовать старый SIDs, чтобы найти необходимые новые источники ссылки, мы открываем исходную модель, чтобы обнаружить исходный блок path и имя, затем определяем местоположение соответствующего заменяющего блока в обновленной модели.

См. скрипт в качестве примера ниже. Когда вы запускаете этот скрипт, он сообщает о 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 команда удалит все Требования и данные о Ссылках из памяти сеанса работы с 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');