Define Edit-Time Checks to Comply with Conditions That You Specify with the Model Advisor
In this example, you create three custom edit-time checks that check for compliance with certain software design standards. Custom edit-time checks help you catch issues earlier in the model design review process, but these checks also report issues in the Model Advisor.
The first check checks that Inport and Outport blocks have certain colors depending on their output data types.
The second check checks whether a Trigger block is higher than other blocks in a subsystem. This check must check edited blocks and other blocks in the same subsystem as the Trigger block.
The third check checks whether signals that connect to Outport blocks have labels.
Register and Define the Custom Edit-Time Checks
1. To register a custom edit-time check, create an sl_customization
function. For this example, copy the sl_customization_cec.m
file to the sl_customization.m
file by entering this command at the command prompt:
copyfile sl_customization_cec.m sl_customization.m f
The sl_customization
function accepts one argument, a customization manager object. To register the custom check, use the addModelAdvisorCheckFcn
method. The input to this method is a handle to the check definition function. For this example, defineCheck
is the check definition function. Open and inspect the sl_customization.m
file.
function sl_customization(cm)
cm.addModelAdvisorCheckFcn(@defineCheck);
2. Create the check definition function. For this example, open and inspect the defineCheck
function. This function contains three ModelAdvisor.Check
objects with their check IDs as input arguments. The CallbackHandle
property is the name of the class that you create to define the edit-time check. For this example, MyEditTimeChecks
is the namespace and PortColor
, TriggerBlockPosition
, and SignalLabel
are the class names. The mdladvRoot.publish
function publishes the checks to a new folder in the Model Advisor. For this example, the folder name is DEMO: Edit Time Checks.
function defineCheck %% Check the background color of Inport and Outport blocks. rec = ModelAdvisor.Check("advisor.edittimecheck.PortColor"); rec.Title = 'Check color of Inport and Outport blocks'; rec.CallbackHandle = 'MyEditTimeChecks.PortColor'; mdladvRoot = ModelAdvisor.Root; mdladvRoot.publish(rec,'DEMO: Edit Time Checks'); %% Check that determines whether Trigger block is the top-most block in a subsystem. rec= ModelAdvisor.Check("advisor.edittimecheck.TriggerBlock"); rec.Title = 'Check that Trigger block position is higher than other blocks'; rec.CallbackHandle = 'MyEditTimeChecks.TriggerBlockPosition'; mdladvRoot.publish(rec,'DEMO: Edit Time Checks'); %% Check that determines whether signals with SignalPropagation 'on' have labels. rec = ModelAdvisor.Check("advisor.edittimecheck.SignalLabel"); rec.Title = 'Check that signals have labels if they are to propagate those labels'; rec.CallbackHandle = 'MyEditTimeChecks.SignalLabels'; mdladvRoot.publish(rec,'DEMO: Edit Time Checks');
3. Create an edit-time check by creating a class that derives from the ModelAdvisor.EdittimeCheck
abstract base class. Inspect the first edit-time check by opening the PortColor.m
file.
classdef PortColor < ModelAdvisor.EdittimeCheck % Check that ports conform to software design standards for background color. % % Background Color Data Types % orange Boolean % green all floating-point % cyan all integers % Light Blue Enumerations and Bus Objects % white auto % methods function obj=PortColor(checkId) obj=obj@ModelAdvisor.EdittimeCheck(checkId); obj.traversalType = edittimecheck.TraversalTypes.BLKITER; end function violation = blockDiscovered(obj, blk) violation = []; if strcmp(get_param(blk,'BlockType'),'Inport') || strcmp(get_param(blk,'BlockType'),'Outport') dataType = get_param(blk,'OutDataTypeStr'); currentBgColor = get_param(blk,'BackgroundColor'); if strcmp(dataType,'boolean') if ~strcmp(currentBgColor, 'orange') % Create a violation object using the ModelAdvisor.ResultDetail class. violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with Boolean outputs should be orange.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end elseif any(strcmp({'single','double'},dataType)) if ~strcmp(currentBgColor, 'green') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with floating-point outputs should be green.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end elseif any(strcmp({'uint8','uint16','uint32','int8','int16','int32'}, dataType)) if ~strcmp(currentBgColor, 'cyan') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with integer outputs should be cyan.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end elseif contains(dataType,'Bus:') if ~strcmp(currentBgColor, 'lightBlue') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with bus outputs should be light blue.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end elseif contains(dataType,'Enum:') if ~strcmp(currentBgColor, 'lightBlue') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with enumeration outputs should be light blue.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end elseif contains(dataType, 'auto') if ~strcmp(currentBgColor, 'white') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',Simulink.ID.getSID(blk)); violation.CheckID = obj.checkId; violation.Description = 'Inport/Outport blocks with auto outputs should be white.'; violation.title = 'Port Block Color'; violation.ViolationType = 'warn'; end end end end function violation = finishedTraversal(obj) violation = []; end function success = fix(obj, violation) success = true; dataType = get_param(violation.Data,'OutDataTypeStr'); if strcmp(dataType,'boolean') set_param(violation.Data,'BackgroundColor','orange'); elseif any(strcmp({'single','double'},dataType)) set_param(violation.Data,'BackgroundColor','green'); elseif any(strcmp({'uint8','uint16','uint32','int8','int16','int32'}, dataType)) set_param(violation.Data,'BackgroundColor','cyan'); elseif contains(dataType,'Bus:') || contains(dataType,'Enum:') set_param(violation.Data,'BackgroundColor','lightBlue'); elseif contains(dataType,'auto') set_param(violation.Data,'BackgroundColor','white'); end end end end
The PortColor
class defines three methods: PortColor
, blockDiscovered
, and fix
. The PortColor
method sets the CheckId
and TraversalType
properties. This check has a traversal type of edittimecheck.TraversalTypes.BLKITER
because the check must check newly added and edited blocks, but it does not have to check for affected blocks in the same subsystem or model as the edited or newly added blocks. The blockDiscovered
method contains an algorithm that checks the color of Inport and Outport blocks. Then, because the violation is on a block, the algorithm highlights a violating block by creating a ModelAdvisor.ResultDetail
violation object with the Type
property set to the default value of SID
. The fix
method updates blocks that do not have correct colors.
4. Inspect the second edit-time check by opening the TriggerBlockPosition.m file.
classdef TriggerBlockPosition < ModelAdvisor.EdittimeCheck properties TriggerBlock = []; position = []; end methods function obj=TriggerBlockPosition(checkId) obj=obj@ModelAdvisor.EdittimeCheck(checkId); obj.traversalType = edittimecheck.TraversalTypes.ACTIVEGRAPH; end function violation = blockDiscovered(obj, blk) violation = []; if strcmp(get_param(blk,'BlockType'),'TriggerPort') obj.TriggerBlock = blk; else h = get_param(blk,'Position'); obj.position = [obj.position, h(2)]; end end function violation = finishedTraversal(obj) violation = []; if isempty(obj.TriggerBlock) return; end triggerPosition = get_param(obj.TriggerBlock,'Position'); if min(obj.position) < triggerPosition(2) violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'SID',... Simulink.ID.getSID(obj.TriggerBlock)); violation.CheckID = obj.checkId; violation.title = 'Trigger Block Position'; violation.Description = 'Trigger Block should be top block in subsystem'; violation.ViolationType = 'warn'; end obj.TriggerBlock = []; obj.position =[]; end end end
The TriggerBlockPosition
class defines three methods: TriggerBlockPosition
, blockDiscovered
, and finishedTraversal
. The TriggerBlockPosition
method sets the CheckId
and TraversalType
properties. This check has a traversal type of edittimecheck.TraversalTypes.ACTIVEGRAPH
because it must check other blocks in the same subsystem as the Trigger block. The blockDiscovered
method checks the position of Trigger blocks within subsystems. The finishedTraversal
method checks whether the position of these Trigger blocks are higher than other blocks in a subsystem. Then, because the violation is on a block, the algorithm highlights a violating block by creating a ModelAdvisor.ResultDetail
violation object with the Type
property set to the default value of SID
.
5. Inspect the third edit-time check by opening the SignalLabels.m
file.
classdef SignalLabels < ModelAdvisor.EdittimeCheck methods function obj=SignalLabels(checkId) obj=obj@ModelAdvisor.EdittimeCheck(checkId); obj.traversalType = edittimecheck.TraversalTypes.BLKITER; end function violation = blockDiscovered(obj, blk) violation = []; ports = get_param(blk,'Ports'); lh = get_param(blk, 'LineHandles'); if strcmp(get_param(blk,'BlockType'),'Outport') for j = 1 : ports(1) if lh.Inport(j) ~= -1 % failure case: no connection allsources = get_param(lh.Inport(j),'SrcPortHandle'); hiliteHandle = get_param(lh.Inport(j), 'DstPortHandle'); if (isempty(allsources) ~= 0) || (isempty(find(allsources==-1,1)) ~= 0) lh_obj = get_param(lh.Inport(j),'Object'); if isempty(lh_obj.Name) if strcmp(lh_obj.signalPropagation,'off') == 1 allsources_parent = get_param(allsources,'Parent'); if strcmp(get_param(allsources_parent,'BlockType'),'Inport') buscreator_outputs = get_param(allsources_parent,'IsBusElementPort'); else buscreator_outputs = 'off'; end if ~strcmp(buscreator_outputs,'on') violation = ModelAdvisor.ResultDetail; ModelAdvisor.ResultDetail.setData(violation,'Signal',hiliteHandle); violation.Description ='This signal should have a label.'; violation.CheckID = obj.checkId; violation.Title = 'Signal Label Missing'; end end end end end end end end end end
The SignalLabels
class defines two methods: SignalLabels
and blockDiscovered
. The SignalLabels
method sets the CheckId
and TraversalType
properties. This check has a traversal type of edittimecheck.TraversalTypes.BLKITER
because the check must check newly added and edited blocks, but it does not have to check for affected blocks in the same subsystem or model as the edited or newly added blocks. Because this check is for signals, the blockDiscovered
method must use the parameters on the line handles, LineHandles
, of blocks to find signals with violations. Specifically, for signals that connect to Outport blocks, this algorithm checks whether the Name
signal parameter has a value. Then, because the violation is on a signal, the algorithm highlights the signal by creating a violation object with the Type
property value set to Signal
.
6. Create a folder named +MyEditTimeChecks
and save the three class definition files in that folder. Classes must be in a folder that has the same name as their namespace. Enter these commands at the command prompt:
copyfile PortColor.m* +MyEditTimeChecks f copyfile TriggerBlockPosition.m +MyEditTimeChecks copyfile SignalLabels.m +MyEditTimeChecks
Run the Edit-Time Checks on a Model
1. In order for your custom checks to be visible in the Model Advisor, you must refresh the Model Advisor check information cache. Enter this command at the command prompt:
Advisor.Manager.refresh_customizations
2. Open the model by entering this command at the command prompt.
open_system('AdvisorCustomizationExample.slx');
3. To create a custom configuration of checks, open the Model Advisor Configuration Editor. On the Modeling tab, click Model Advisor > Configuration Editor.
4. You can create a custom configuration consisting of just the three edit-time checks in this example by deleting every folder except for the DEMO: Edit Time Checks folder. The my_config.json
file that ships with this example contains this configuration. Close the Model Advisor Configuration Editor.
5 Set the custom configuration to the my_config.json
file by clicking the Modeling tab and selecting Model Advisor > Edit-Time Checks. In the Configuration Parameters dialog box, specify the path to the configuration file in the Model Advisor configuration file parameter. Alternatively, enter this command at the command prompt:
ModelAdvisor.setModelConfiguration('AdvisorCustomizationExample', 'my_config.json');
6. Turn on edit-time checking by clicking the Modeling tab and selecting Model Advisor > Edit-Time Checks. In the Configuration Parameters dialog box, select the Edit-Time Checks parameter. Alternatively, you can enter this command at the command prompt:
edittime.setAdvisorChecking('AdvisorCustomizationExample','on');
7. Open the Model Advisor by clicking the Modeling tab and selecting Model Advisor. Observe that the three edit-time checks are the only ones in the Model Advisor. Close the Model Advisor.
8. To view the edit-time warnings, click the blocks and signals highlighted in yellow.
At the top level of the model, the two Inport blocks have an output data type of int32
. They produce edit-time warnings because they should be cyan. The Outport block does not produce a violation because it has an auto
data type and is white. In the Amplifier subsystem, the Inport and Outport blocks do not produce edit-time warnings because they have a data type of auto
and are white. The Trigger block does not produce an edit-time warning because it is the top-most block in the model. If you move the Trigger block below another block, the Trigger block has an edit-time warning. The signal connecting to the Outport block produces a warning because it does not have a label.
9. To fix the edit-time warnings for the two Inport blocks, in the edit-time check warning window, click Fix.
10. Close the model and the Model Advisor.
bdclose;
10. Remove the files from your working directory. Refresh the Model Advisor check information cache by entering this command:
Advisor.Manager.refresh_customizations
Performance Considerations for Custom Edit-Time Checks
To help prevent custom edit-time checks from negatively impacting performance as you edit your model, the Model Advisor automatically disables custom edit-time checks if, in the current MATLAB® session, the check takes longer than 500
milliseconds to execute in at least three different Simulink® models.
If the Model Advisor disables a custom edit-time check, you will see a warning on the Simulink canvas. You can re-enable the edit-time check by either:
Clicking the hyperlink text in the warning.
Passing the check identifier,
checkID
, to the functionedittime.enableCheck
:
edittime.enableCheck(checkID)
To prevent a custom edit-time check from being disabled, author the check so that the check executes in less than 500
milliseconds on your models.
See Also
ModelAdvisor.EdittimeCheck
| ModelAdvisor.Check
| ModelAdvisor.ResultDetail