classdef HSVClosedLoopAppAUTOMATIC < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
GridLayout matlab.ui.container.GridLayout
AutomaticTargettingButton matlab.ui.control.Button
SelectionPanel matlab.ui.container.Panel
DeleteButton matlab.ui.control.Button
InitializeButton matlab.ui.control.Button
foregroundFigure matlab.ui.control.UIAxes
end
% Public properties that correspond to the Simulink model
properties (Access = public, Transient)
Simulation simulink.Simulation
end
properties (Access = private)
initialized = false;
occupiedPins;
pinLocs;
end
properties (Access = public)
foreGnd;
end
methods (Access = private)
% getLaneCalibration
function [laneLeft_px, laneRight_px, laneCenter_px, inchesPerPixel] = ...
getLaneCalibration(~, foreImg, laneWidth_in)
% Load lane calibration from laneCalibration.mat if it exists
% and matches the current image size + lane width.
% Otherwise, ask the user to click LEFT and RIGHT lane edges once, then
% save for future runs.
fname = 'laneCalibration.mat';
[hNow, wNow, ~] = size(foreImg);
imgSizeNow = [hNow, wNow];
needRecal = true;
app.initialized = true;
if isfile(fname)
S = load(fname);
if isfield(S, 'laneLeft_px') && isfield(S, 'laneRight_px') && ...
isfield(S, 'laneCenter_px') && isfield(S, 'inchesPerPixel') && ...
isfield(S, 'imgSize') && isfield(S, 'laneWidth_in_save')
if isequal(S.imgSize, imgSizeNow) && S.laneWidth_in_save == laneWidth_in
laneLeft_px = S.laneLeft_px;
laneRight_px = S.laneRight_px;
laneCenter_px = S.laneCenter_px;
inchesPerPixel = S.inchesPerPixel;
fprintf('Loaded lane calibration from %s\n', fname);
needRecal = false;
else
fprintf('Lane calibration image size or lane width changed. Recalibrating.\n');
end
else
fprintf('Lane calibration file missing fields. Recalibrating.\n');
end
end
if needRecal
figure;
imshow(foreImg);
title('Click LEFT lane edge, then RIGHT lane edge');
[xEdge, ~] = ginput(2);
close;
laneLeft_px_meas = xEdge(1);
laneRight_px_meas = xEdge(2);
laneLeft_px = min(laneLeft_px_meas, laneRight_px_meas);
laneRight_px = max(laneRight_px_meas, laneLeft_px_meas);
laneCenter_px = (laneLeft_px + laneRight_px) / 2;
pixelSpan = abs(laneRight_px - laneLeft_px);
inchesPerPixel = laneWidth_in / pixelSpan;
imgSize = imgSizeNow; %#ok<NASGU>
laneWidth_in_save = laneWidth_in; %#ok<NASGU>
save(fname, 'laneLeft_px','laneRight_px','laneCenter_px', ...
'inchesPerPixel','imgSize','laneWidth_in_save');
fprintf('Saved lane calibration to %s\n', fname);
end
end
% pinDotCenters_px
function pinDotCenters_px = getPinDotCenters(~, foreImg)
% If pinDotCenters.mat exists and matches current image size, it loads.
% Otherwise, it shows the image and asks you to click each dot center.
fname = 'pinDotCenters.mat';
[hNow, wNow, ~] = size(foreImg);
imgSizeNow = [hNow, wNow];
needRecal = true;
if isfile(fname)
S = load(fname);
if isfield(S, 'pinDotCenters_px') && isfield(S, 'imgSize')
if isequal(S.imgSize, imgSizeNow)
% same orientation/size -> reuse
pinDotCenters_px = S.pinDotCenters_px;
fprintf('Loaded %d stored pin dot locations from %s\n', ...
size(pinDotCenters_px,1), fname);
needRecal = false;
else
fprintf(['Image size/orientation changed since last ' ...
'calibration. Re-calibrating pin dot centers...\n']);
end
else
fprintf('Old calibration file found but missing fields. Re-calibrating.\n');
end
end
if needRecal
figure;
imshow(foreImg);
title('Calibration: click the center of each dot (e.g., 6 clicks)');
numDots = 6;
[x, y] = ginput(numDots);
pinDotCenters_px = [x, y];
imgSize = imgSizeNow; %#ok<NASGU>
save(fname, 'pinDotCenters_px', 'imgSize');
fprintf('Saved %d pin dot locations to %s\n', numDots, fname);
close;
end
end
% sendRoatationsToMotor
function sendRotationsToMotor(app,targetRot)
assignin('base', 'targetRot', targetRot);
assignin('base', 'targetAngle', targetRot * 360); % degrees
assignin('base', 'newTargetRotAvailable', 1);
% Let Simulink pull the new value
set_param('working', 'SimulationCommand', 'update');
fprintf('targetRot = %.3f rev sent to Simulink workspace\n', targetRot);
startSimulinkModel(app);
end
% startSimulink Model
function startSimulinkModel(~)
modelName = 'working';
if ~bdIsLoaded(modelName)
try
load_system(modelName);
fprintf('Simulink model "%s" loaded\n', modelName);
catch
fprintf('Could not load Simulink model "%s". Check model name.\n', modelName);
return;
end
end
try
set_param(modelName, 'SimulationCommand', 'start');
fprintf('Simulation "%s" started\n', modelName);
catch ME
fprintf('Could not start simulation: %s\n', ME.message);
end
end
% moveMotorToPin
function targetRot = moveMotorToPin(app,objectLoc, fig, motorPosition) %#ok<INUSD>
fprintf("Calculating motor rotations for selected pin...\n");
laneCenter_px = evalin('base', 'laneCenter_px');
inchesPerPixel = evalin('base', 'inchesPerPixel');
travelPerRev_in = evalin('base', 'travelPerRev_in');
maxRotations = evalin('base', 'maxRotations');
halfLane_in = evalin('base', 'halfLane_in');
motorDirSign = evalin('base','motorDirectionSign');
pinX_px = objectLoc(1);
pinY_px = objectLoc(2); %#ok<NASGU>
fprintf("Pin centroid (pixels): (%.1f, %.1f)\n", pinX_px, pinY_px);
fprintf("Lane center (pixels): %.1f\n", laneCenter_px);
% Pixels -> inches from lane center
offset_in = (pinX_px - laneCenter_px) * inchesPerPixel; % + right, - left
fprintf("Desired offset from lane center: %.3f in\n", offset_in);
% Clamp to physical lane edges
offset_in_clamped = max(min(offset_in, halfLane_in), -halfLane_in);
if abs(offset_in - offset_in_clamped) > 1e-3
fprintf("Offset clamped from %.3f in to %.3f in (lane edge)\n", ...
offset_in, offset_in_clamped);
end
% Inches -> rotations, with adjustable direction
targetRot = motorDirSign * (offset_in_clamped / travelPerRev_in);
% Clamp rotations to mechanical limit
targetRot_clamped = max(min(targetRot, maxRotations), -maxRotations);
if abs(targetRot - targetRot_clamped) > 1e-3
fprintf("Rotations clamped from %.3f rev to %.3f rev (rack limit)\n", ...
targetRot, targetRot_clamped);
end
targetRot = targetRot_clamped;
fprintf("Commanded target rotations from center: %.3f rev\n", targetRot);
% Move laterally to the pin ===
sendRotationsToMotor(app,targetRot);
% Wait 3 seconds at that lateral position ===
pause(3);
% Fire the servo motor action ===
triggerServoMotor();
% Wait another 3 seconds after servo action ===
pause(3);
% Return to original lateral position (center = 0 rev) ===
fprintf("Returning to center (0 rev)...\n");
sendRotationsToMotor(app,0);
pause(4);
cam = webcam('Brio 100');
try
cam.ExposureMode = 'auto';
catch
disp('Capturing game board...');
end
rawImg = snapshot(cam);
% Orientation so that ball travels bottom->top and lane left/right match view
fore = rot90(rawImg, 1);
fore = flipud(fore); % vertical flip
fore = fliplr(fore); % horizontal mirror
imshow(fore,'Parent',app.foregroundFigure);
% Get stored dot centers (or calibrate once if file doesn't exist)
pinDotCenters_px = getPinDotCenters(app, fore); % Nx2 [x,y]
app.pinLocs = pinDotCenters_px;
% Convert to HSV once
hsvImg = rgb2hsv(fore);
s = hsvImg(:,:,2);
v = hsvImg(:,:,3);
% Window radius around each dot
searchRadius = 30; % local patch half-size
N = size(pinDotCenters_px,1);
meanS_all = zeros(N,1);
meanV_all = zeros(N,1);
fprintf("\n--- Per-pin saturation debug ---\n");
for i = 1:N
cx = pinDotCenters_px(i,1);
cy = pinDotCenters_px(i,2);
xRange = round(cx - searchRadius) : round(cx + searchRadius);
yRange = round(cy - searchRadius) : round(cy + searchRadius);
xRange = xRange(xRange >= 1 & xRange <= size(s,2));
yRange = yRange(yRange >= 1 & yRange <= size(s,1));
sPatch = s(yRange, xRange);
vPatch = v(yRange, xRange);
meanS_all(i) = mean(sPatch(:));
meanV_all(i) = mean(vPatch(:));
fprintf('Pin %d: meanS = %.3f, meanV = %.3f\n', i, meanS_all(i), meanV_all(i));
end
% Use the median saturation as "background paper" level
bgS = 0.65;
app.occupiedPins = false(N,1);
for i = 1:N
% pin if it's noticeably more saturated than the typical patch
if meanS_all(i) > bgS + 0.05 && meanV_all(i) > 0.4
app.occupiedPins(i) = true;
end
end
fprintf('--------------------------------------\n');
numDetected = nnz(app.occupiedPins);
fprintf('Detected %d occupied pin location(s).\n', numDetected);
fig = app.SelectionPanel;
figWidth = 320;
figHeight = 260;
fig.Position(3:4) = [figWidth figHeight];
uilabel(fig, ...
'Text', 'Select a pin to target', ...
'Position', [20, figHeight-40, figWidth-40, 30], ...
'HorizontalAlignment', 'center', ...
'FontSize', 14);
numDots = size(pinDotCenters_px,1);
centerX = figWidth/2;
rowY_top = figHeight - 90;
rowSpacing = 45;
btnSize = 40;
btnPos = nan(numDots, 4);
y_top = rowY_top;
y_middle = rowY_top - rowSpacing;
y_bottom = rowY_top - 2*rowSpacing;
if numDots >= 1
btnPos(1,:) = [centerX - btnSize/2, y_bottom, btnSize, btnSize];
end
if numDots >= 3
offsetX = 35;
btnPos(2,:) = [centerX - offsetX - btnSize/2, y_middle, btnSize, btnSize];
btnPos(3,:) = [centerX + offsetX - btnSize/2, y_middle, btnSize, btnSize];
end
if numDots >= 6
offsetX = 70;
btnPos(4,:) = [centerX - offsetX - btnSize/2, y_top, btnSize, btnSize];
btnPos(5,:) = [centerX - btnSize/2, y_top, btnSize, btnSize];
btnPos(6,:) = [centerX + offsetX - btnSize/2, y_top, btnSize, btnSize];
end
delete(fig.Children);
% Create the buttons inside app.SelectionPanel
for i = 1:numDots
if any(isnan(btnPos(i,:)))
continue;
end
thisPos = btnPos(i,:);
btn = uibutton(fig, 'push', ...
'Text', sprintf('%d', i), ...
'Position', thisPos, ...
'ButtonPushedFcn', ...
@(btn,event) moveMotorToPin(app, pinDotCenters_px(i,:), [], struct()), ...
'FontSize', 12, ...
'FontWeight', 'bold');
if app.occupiedPins(i)
btn.Enable = 'on';
btn.BackgroundColor = [0.2 0.8 0.2];
else
btn.Enable = 'off';
btn.BackgroundColor = [0.8 0.8 0.8];
end
end
if numDetected == 0
noteTxt = 'No pins detected at any locations.';
else
noteTxt = sprintf('%d pin(s) detected. Grey pins = empty spots.', numDetected);
end
uilabel(fig, ...
'Text', noteTxt, ...
'Position', [20, 15, figWidth-40, 30], ...
'HorizontalAlignment', 'center', ...
'FontSize', 10);
if(numDetected)
app.InitializeButton.BackgroundColor = 'green';
app.InitializeButton.Text = 'Update Pins';
app.AutomaticTargettingButton.Enable = 'on';
app.AutomaticTargettingButton.BackgroundColor = 'blue';
app.AutomaticTargettingButton.FontColor = 'black';
else
app.InitializeButton.BackgroundColor = 'red';
app.InitializeButton.Text = 'Update Pins';
app.AutomaticTargettingButton.Enable = 'off';
app.AutomaticTargettingButton.BackgroundColor = [0.13,0.13,0.13];
app.AutomaticTargettingButton.FontColor = 'white';
end
% within moveMotorToPin
function triggerServoMotor()
fprintf("Triggering servo via Simulink (servoAngle variable)...\n");
% Go to 90 degrees
assignin('base','servoAngle',32);
set_param('working','SimulationCommand','update');
pause(2);
% Back to 0 degrees
assignin('base','servoAngle',0);
set_param('working','SimulationCommand','update');
fprintf("Servo returned to rest (0°).\n");
end
end
end
% Callbacks that handle component events
methods (Access = private)
% Button pushed function: InitializeButton
function InitializeButtonPushed(app, event)
motorDirectionSign = -1; % if left/right are inverted, use -1. If it flips, change to +1.
assignin('base','motorDirectionSign', motorDirectionSign);
assignin('base','servoAngle',0); % initial rest angle
% Lane geometry
laneWidth_in = 3.85; % total lane width in inches
halfLane_in = laneWidth_in/2;
% Gear / rack parameters (Mod 1, 16-tooth spur gear)
module_mm = 1; % metric module (mm per tooth)
gearTeeth = 16;
mm_per_in = 25.4;
pitchDiameter_mm = module_mm * gearTeeth; % d = m*z
travelPerRev_mm = pi * pitchDiameter_mm; % linear travel per revolution
travelPerRev_in = travelPerRev_mm / mm_per_in; % in/rev
maxRotations = halfLane_in / travelPerRev_in; % max rev from center to side
fprintf('Travel per rev: %.3f in\n', travelPerRev_in);
fprintf('Max rotations from center: %.3f rev\n', maxRotations);
cam = webcam('Brio 100');
% Let the camera handle exposure automatically.
try
cam.ExposureMode = 'auto';
catch
disp('Could not set exposure mode (OK to ignore).');
end
% Variables used by Simulink (in base workspace)
assignin('base','targetRot', 0); % revolutions from center
assignin('base','targetAngle', 0); % degrees from center
assignin('base','newTargetRotAvailable', 0);
assignin('base','currentRot', 0);
assignin('base', 'travelPerRev_in', travelPerRev_in);
assignin('base', 'maxRotations', maxRotations);
assignin('base', 'halfLane_in', halfLane_in);
fprintf('Initialized workspace variables for Simulink.\n');
rawImg = snapshot(cam);
% Orientation so that ball travels bottom->top and lane left/right match view
fore = rot90(rawImg, 1);
fore = flipud(fore); % vertical flip
fore = fliplr(fore); % horizontal mirror
gameState.foreGnd = fore;
imshow(fore,'Parent',app.foregroundFigure);
[laneLeft_px, laneRight_px, laneCenter_px, inchesPerPixel] = ...
getLaneCalibration(app, gameState.foreGnd, laneWidth_in);
fprintf('\n--- Lane Calibration ---\n');
fprintf('Lane left pixel: %.1f\n', laneLeft_px);
fprintf('Lane right pixel: %.1f\n', laneRight_px);
fprintf('Lane center pixel: %.1f\n', laneCenter_px);
fprintf('Inches per pixel: %.5f in/px\n', inchesPerPixel);
% Expose to base for moveMotorToPin
assignin('base', 'laneLeft_px', laneLeft_px);
assignin('base', 'laneRight_px', laneRight_px);
assignin('base', 'laneCenter_px', laneCenter_px);
assignin('base', 'inchesPerPixel', inchesPerPixel);
% Get stored dot centers (or calibrate once if file doesn't exist)
pinDotCenters_px = getPinDotCenters(app, gameState.foreGnd); % Nx2 [x,y]
app.pinLocs = pinDotCenters_px;
% Convert to HSV once
hsvImg = rgb2hsv(gameState.foreGnd);
s = hsvImg(:,:,2);
v = hsvImg(:,:,3);
% Window radius around each dot
searchRadius = 30; % local patch half-size
N = size(pinDotCenters_px,1);
meanS_all = zeros(N,1);
meanV_all = zeros(N,1);
fprintf("\n--- Per-pin saturation debug ---\n");
for i = 1:N
cx = pinDotCenters_px(i,1);
cy = pinDotCenters_px(i,2);
xRange = round(cx - searchRadius) : round(cx + searchRadius);
yRange = round(cy - searchRadius) : round(cy + searchRadius);
xRange = xRange(xRange >= 1 & xRange <= size(s,2));
yRange = yRange(yRange >= 1 & yRange <= size(s,1));
sPatch = s(yRange, xRange);
vPatch = v(yRange, xRange);
meanS_all(i) = mean(sPatch(:));
meanV_all(i) = mean(vPatch(:));
fprintf('Pin %d: meanS = %.3f, meanV = %.3f\n', i, meanS_all(i), meanV_all(i));
end
% Use the median saturation as "background paper" level
bgS = 0.65;
app.occupiedPins = false(N,1);
for i = 1:N
% pin if it's noticeably more saturated than the typical patch
if meanS_all(i) > bgS + 0.05 && meanV_all(i) > 0.4
app.occupiedPins(i) = true;
end
end
fprintf('--------------------------------------\n');
numDetected = nnz(app.occupiedPins);
fprintf('Detected %d occupied pin location(s).\n', numDetected);
fig = app.SelectionPanel;
figWidth = 320;
figHeight = 260;
fig.Position(3:4) = [figWidth figHeight];
uilabel(fig, ...
'Text', 'Select a pin to target', ...
'Position', [20, figHeight-40, figWidth-40, 30], ...
'HorizontalAlignment', 'center', ...
'FontSize', 14);
numDots = size(pinDotCenters_px,1);
centerX = figWidth/2;
rowY_top = figHeight - 90;
rowSpacing = 45;
btnSize = 40;
btnPos = nan(numDots, 4);
y_top = rowY_top;
y_middle = rowY_top - rowSpacing;
y_bottom = rowY_top - 2*rowSpacing;
if numDots >= 1
btnPos(1,:) = [centerX - btnSize/2, y_bottom, btnSize, btnSize];
end
if numDots >= 3
offsetX = 35;
btnPos(2,:) = [centerX - offsetX - btnSize/2, y_middle, btnSize, btnSize];
btnPos(3,:) = [centerX + offsetX - btnSize/2, y_middle, btnSize, btnSize];
end
if numDots >= 6
offsetX = 70;
btnPos(4,:) = [centerX - offsetX - btnSize/2, y_top, btnSize, btnSize];
btnPos(5,:) = [centerX - btnSize/2, y_top, btnSize, btnSize];
btnPos(6,:) = [centerX + offsetX - btnSize/2, y_top, btnSize, btnSize];
end
% Create the buttons inside app.SelectionPanel
delete(fig.Children);
for i = 1:numDots
if any(isnan(btnPos(i,:)))
continue;
end
thisPos = btnPos(i,:);
btn = uibutton(fig, 'push', ...
'Text', sprintf('%d', i), ...
'Position', thisPos, ...
'ButtonPushedFcn', ...
@(btn,event) moveMotorToPin(app, pinDotCenters_px(i,:), [], struct()), ...
'FontSize', 12, ...
'FontWeight', 'bold');
if app.occupiedPins(i)
btn.Enable = 'on';
btn.BackgroundColor = [0.2 0.8 0.2];
else
btn.Enable = 'off';
btn.BackgroundColor = [0.8 0.8 0.8];
end
end
if numDetected == 0
noteTxt = 'No pins detected at any locations.';
else
noteTxt = sprintf('%d pin(s) detected. Grey pins = empty spots.', numDetected);
end
uilabel(fig, ...
'Text', noteTxt, ...
'Position', [20, 15, figWidth-40, 30], ...
'HorizontalAlignment', 'center', ...
'FontSize', 10);
if(numDetected)
app.InitializeButton.BackgroundColor = 'green';
app.InitializeButton.Text = 'Update Pins';
app.AutomaticTargettingButton.Enable = 'on';
app.AutomaticTargettingButton.BackgroundColor = 'blue';
app.AutomaticTargettingButton.FontColor = 'black';
else
app.InitializeButton.BackgroundColor = 'red';
app.InitializeButton.Text = 'Update Pins';
app.AutomaticTargettingButton.Enable = 'off';
app.AutomaticTargettingButton.BackgroundColor = [0.13,0.13,0.13];
app.AutomaticTargettingButton.FontColor = 'white';
end
end
% Button pushed function: DeleteButton
function DeleteButtonPushed(app, event)
if(isfile('laneCalibration.mat'))
delete('laneCalibration.mat')
end
if(isfile('pinDotCenters.mat'))
delete('pinDotCenters.mat')
end
delete(app.SelectionPanel.Children);
cla(app.foregroundFigure);
app.InitializeButton.BackgroundColor = [0.07,0.44,0.75];
app.InitializeButton.Text = "Initialize";
end
% Button pushed function: AutomaticTargettingButton
function AutomaticTargettingButtonPushed(app, event)
knockMatrix = {
[1 2 3],
[2 4],
[3 6],
[4],
[5],
[6]
};
scores = zeros(1,6);
for i = 1:6
if app.occupiedPins(i) == 0
scores(i) = -inf; % cannot target an empty pin
continue;
end
knocked = knockMatrix{i};
scores(i) = sum(app.occupiedPins(knocked));
end
% choose pin with maximum expected pins hit
[~, bestPin] = max(scores);
if scores(bestPin) < 0
bestPin = nan; % nothing found
end
if(bestPin)
moveMotorToPin(app, app.pinLocs(bestPin,:), [], struct())
end
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 651 474];
app.UIFigure.Name = 'MATLAB App';
% Create GridLayout
app.GridLayout = uigridlayout(app.UIFigure);
app.GridLayout.ColumnWidth = {'1x', '1.84x', 151, '1.88x', '1.28x', 66, 127, 67, '1x'};
app.GridLayout.RowHeight = {'1x', '1x', 251, '1x', 46, '1.58x', 23, 23, '1x'};
app.GridLayout.RowSpacing = 9.14285714285714;
app.GridLayout.Padding = [10 9.14285714285714 10 9.14285714285714];
% Create foregroundFigure
app.foregroundFigure = uiaxes(app.GridLayout);
app.foregroundFigure.Layout.Row = [3 6];
app.foregroundFigure.Layout.Column = [2 4];
% Create InitializeButton
app.InitializeButton = uibutton(app.GridLayout, 'push');
app.InitializeButton.ButtonPushedFcn = createCallbackFcn(app, @InitializeButtonPushed, true);
app.InitializeButton.BackgroundColor = [0.0706 0.4392 0.749];
app.InitializeButton.FontColor = [0 0 0];
app.InitializeButton.Layout.Row = [7 8];
app.InitializeButton.Layout.Column = 7;
app.InitializeButton.Text = 'Initialize';
% Create DeleteButton
app.DeleteButton = uibutton(app.GridLayout, 'push');
app.DeleteButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteButtonPushed, true);
app.DeleteButton.BackgroundColor = [1 0 0];
app.DeleteButton.FontColor = [0 0 0];
app.DeleteButton.Layout.Row = 7;
app.DeleteButton.Layout.Column = 3;
app.DeleteButton.Text = 'Delete Pin and Lane Data';
% Create SelectionPanel
app.SelectionPanel = uipanel(app.GridLayout);
app.SelectionPanel.TitlePosition = 'centertop';
app.SelectionPanel.Title = 'Manual Pin Selection';
app.SelectionPanel.Layout.Row = 3;
app.SelectionPanel.Layout.Column = [6 8];
% Create AutomaticTargettingButton
app.AutomaticTargettingButton = uibutton(app.GridLayout, 'push');
app.AutomaticTargettingButton.ButtonPushedFcn = createCallbackFcn(app, @AutomaticTargettingButtonPushed, true);
app.AutomaticTargettingButton.FontColor = [1 1 1];
app.AutomaticTargettingButton.Enable = 'off';
app.AutomaticTargettingButton.Layout.Row = 5;
app.AutomaticTargettingButton.Layout.Column = 7;
app.AutomaticTargettingButton.Text = 'Automatic Targetting';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = HSVClosedLoopAppAUTOMATIC
% Associate the Simulink Model
app.Simulation = simulation('working');
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end
end