% Generates Simulink compliance test data for the PID controller implementation
% Copyright © 2025 Hs293Go
%
% Permission is hereby granted, free of charge, to any person obtaining
% a copy of this software and associated documentation files (the "Software"),
% to deal in the Software without restriction, including without limitation
% the rights to use, copy, modify, merge, publish, distribute, sublicense,
% and/or sell copies of the Software, and to permit persons to whom the
% Software is furnished to do so, subject to the following conditions:
%
% The above copyright notice and this permission notice shall be included
% in all copies or substantial portions of the Software.
%
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
% OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
%% General settings
% This script is meant to generate a model on the fly,
% therefore, configure simulink to write slxc and slprj to a temporary folder
cruft_dir = [tempdir 'pid_test'];
if ~exist(cruft_dir, "dir")
mkdir(cruft_dir);
end
set_param(0, 'CacheFolder', cruft_dir);
set_param(0, 'CodeGenFolder', cruft_dir);
set_param(0, 'AutoSaveOptions', struct('SaveOnModelUpdate', 0))
model = 'pid_controller_simulink_compliance_test';
new_system(model);
load_system(model);
% A number of non-default PID settings to engage every part of our
% implementation
P_GAIN = 10.0;
I_GAIN = 20.0;
D_GAIN = 1.0;
pid_settings = {'P', num2str(P_GAIN), ...
'I', num2str(I_GAIN), ...
'D', num2str(D_GAIN), ...
'Filtermethod', 'Backward Euler', ...
'N', '50'};
%% Define model settings
% We use fixed-step ode1 to ensure data reproducibility
set_param(model, 'Solver','ode1');
set_param(model, 'FixedStep', '0.01');
set_param(model, 'SaveFormat', 'Array'); % Simply stores outputs column by column
%% Define open-loop test model
add_block('simulink/Sources/Sine Wave', [model, '/Sine']);
add_block('simulink/Discrete/Discrete PID Controller', [model, '/ol_PID']);
add_block('simulink/Commonly Used Blocks/Out1', [model, '/ol_output']);
set_param([model, '/ol_PID'], pid_settings{:});
add_line(model, 'Sine/1', 'ol_PID/1');
add_line(model, 'ol_PID/1', 'ol_output/1');
%% Define closed-loop test model
add_block('simulink/Discrete/Discrete PID Controller', [model, '/cl_PID']);
add_block('simulink/Commonly Used Blocks/Sum', [model, '/sum']);
add_block('simulink/Continuous/State-Space', [model, '/cl_system']);
add_block('simulink/Commonly Used Blocks/Out1', [model, '/cl_output']);
set_param([model, '/sum'], 'Listofsigns', '|+-');
set_param([model, '/cl_PID'], pid_settings{:});
omega_n = 2*pi;
zeta = 0.2;
set_param([model, '/cl_system'], ...
'A', '[0, 1; -omega_n^2, -2 * omega_n * zeta]', ...
'b', '[0; omega_n^2]', ...
'c', '[1, 0]', ...
'd', '0');
add_line(model, 'Sine/1', 'sum/1');
add_line(model, 'sum/1', 'cl_PID/1');
add_line(model, 'cl_PID/1', 'cl_system/1');
add_line(model, 'cl_system/1', 'cl_output/1');
add_line(model, 'cl_system/1', 'sum/2');
%% simulate the model
out = sim(model);
close_system(model, 0); % No saving
%% Write data to our rust source file
DOWNSAMPLE_FACTOR = 20;
FIXED_STEP_SIZE = 0.01;
FIXED_STEP_SIZE_MS = FIXED_STEP_SIZE * 1000;
DOWNSAMPLED_INTERVAL = FIXED_STEP_SIZE_MS / DOWNSAMPLE_FACTOR;
fp = fopen("data.rs", "w");
fprintf(fp, "// This file contains embedded test datais generated by generate_test_data.m\n");
fprintf(fp, "#![allow(clippy::all)]\n");
fprintf(fp, "pub const FIXED_STEP_SIZE_MS: u64 = %d;\n", FIXED_STEP_SIZE_MS);
fprintf(fp, "pub const DOWNSAMPLE_FACTOR: usize = %d;\n", DOWNSAMPLE_FACTOR);
fprintf(fp, "#[rustfmt::skip]\npub const OL_SINE_RESPONSE: &[f64] = &[%s];\n", sprintf("%.15f,\n", out.yout(1:DOWNSAMPLE_FACTOR:end, 1)));
fprintf(fp, "#[rustfmt::skip]\npub const CL_SINE_RESPONSE: &[f64] = &[%s];\n", sprintf("%.15f,\n", out.yout(1:DOWNSAMPLE_FACTOR:end, 2)));
fclose(fp);