Этот пример использует 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.
Вы хотите использовать продукт 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
В ходе разработки проектного проекта может возникнуть необходимость перехода к новому набору Требований. Если текущие Требования имеют ссылки, и когда существует известное правило для связи исходных связанных требований с соответствующими записями в новом Наборе требований, можно захотеть автоматически перенести связи, где это возможно, чтобы избежать повторного связывания вручную. Миграция существующих ссылок предпочтительнее повторного создания новых ссылок, потому что вы сохраняете существующие метаданные, такие как ключевые слова, операторы обоснования, история комментариев.
Чтобы быстро собрать пример ситуации, когда вам может потребоваться миграция ссылок, начнем с требований, импортированных из документа 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
Примите во внимание ситуацию, когда у вас есть Подсистема с множеством ссылок на Требования, и эта подсистема должна быть переработана или полностью заменена. Новая реализация в основном аналогична, и вы хотели бы сохранить существующие ссылки там, где это возможно (где блоки с таким же именем существуют на том же уровне иерархии структуры модели). Это позволит ограничить шаги связывания вручную только те блоки, которые не существовали в исходной реализации. Вы используете 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');