В этом примере используются API Simulink ® и Simulink Requirements ® для автоматического захвата и связывания структуры модели Simulink с целью документирования конструкции в редакторе требований Simulink. Автоматизация также поможет восстановить или перенести данные отслеживания требований после замены или изменения связанных артефактов. Показано использование следующих API командной строки:
slreq.new для создания нового набора требований
slreq.ReqSet для добавления записей в набор требований
add для добавления дочерних требований
slreq.Requirement для заполнения Description область
slreq.createLink для создания ссылки из SRC кому DEST
slreq.find для определения местоположения объектов требований Simulink
setDestination для повторного подключения конечного конца существующей линии связи
setSource для перемещения существующей ссылки на новый исходный объект
isResolvedSource для идентификации ссылок, исходный объект которых не может быть найден
slreq.show используется для просмотра исходного или целевого конца данного slreq.Link
В нескольких местах мы также используем наследие rmi API, унаследованные из части интерфейса управления требованиями (RMI) вышедшего из обращения продукта SLVnV.
Необходимо использовать продукт 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 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'];
Теперь мы можем просматривать структуру каждой модели в Редакторе требований (Requirement Editor) и редактировать поля Описание (Description), чтобы предоставить дополнительные сведения о каждом элементе конструкции. Отсутствует простой способ навигации между объектами на диаграммах Simulink и элементами прокси/суррогата в требованиях Simulink. Приведенный ниже сценарий демонстрирует использование slreq.createLink API для автоматического добавления ссылок навигации. Мы можем выбрать любой желаемый уровень гранулярности. В данном примере мы ограничим связывание с блоками 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
В процессе разработки проектного проекта может возникнуть необходимость перехода на новый набор Требований. Если текущие требования имеют ссылки и если существует известное правило, связывающее исходные связанные требования с соответствующими записями в новом наборе требований, может потребоваться автоматическая миграция ссылок, где это возможно, чтобы избежать повторного создания всех ссылок вручную. Перенос существующих ссылок предпочтительнее, чем повторное создание новых ссылок, поскольку существующие метаданные, такие как ключевые слова, операторы обоснования, история комментариев, сохраняются.
Чтобы быстро составить пример ситуации, когда может потребоваться перенос ссылок, начнем с требований, импортированных из документа Microsoft Word, а затем создадим некоторые ссылки.

Затем мы создадим несколько ссылок в интерактивном режиме (перетаскиванием в режиме ракурса требований) или с помощью 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
Рассмотрите ситуацию, когда у вас есть субсистема с большим количеством ссылок на требования, и эта подсистема должна быть переработана или полностью заменена. Новая реализация в основном аналогична, и необходимо сохранить существующие связи там, где это возможно (где блоки с тем же именем существуют на том же уровне иерархии структуры модели). Это позволит ограничить шаги ручного связывания только блоками, которые не существовали в исходной реализации. Вы используете setSource API для повторного присоединения существующих ссылок к новым исходным объектам после замены SubSystem. Обратите внимание, что вы не можете просто продолжать использовать старые связи, так как связи полагаются на уникальные постоянные независимые от сеанса идентификаторы (SID), чтобы связать исходный объект связи (объект Simulink, который «владеет» связью), и ваша заменяющая SubSystem имеет новые SID для каждого объекта.
Демонстрация использования setSource API с нашей примерной моделью, мы просто заменим два блока таблицы истинности, которые мы связали в предыдущем разделе, такими же точно новыми блоками. После этого ссылки становятся unresolved, потому что новые копии Таблицы Истинности имеют новые SID.
В редакторе требований нажмите «Показать связи» и обратите внимание на указатели оранжевого треугольника для всех разорванных связей. Всего 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 и убедитесь, что все ссылки теперь разрешены, оранжевые индикаторы значков отсутствуют.

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