Converting MATLAB Simulink model to Engee model
Introduction
In this example, we will look at the process of automatically transferring user work from MATLAB Simulink to Engee, and compare the result of the transfer by simulating the original and the resulting models.
Obtaining a script for building the model
The first step in transferring a model from Simulink is to automatically get the script to build the Engee model using command control. Let's start by defining the variables for the files in this example:
путь_примера = "$(@__DIR__)" # Путь к папке примера
имя_модели = "alphabetafilter" # Имя моделей
модельSLX = путь_примера*"/slx/"*имя_модели*".slx"; # Путь к исходной модели
mkdir(joinpath(путь_примера,"engee")); # Создаём папку для результирующей модели
скриптJL = путь_примера*"/engee/convert_script.jl"; # Путь к результирующему скрипту
модельENGEE = путь_примера*"/engee/"*имя_модели*".engee"; # Путь к результирующей модели
The MATLAB Simulink model we use in this example is alphabetafilter.slx from the integrating C code into the Engee model example.
Now let's use the programme control function engee.convert_model().
This function first defines the contents of the source model -
subsystems, blocks, parameters, connections of blocks and subsystems.
Then, based on the obtained data, it creates a script .jl with programme control commands to build a new model .engee.
engee.convert_model(модельSLX, скриптJL); # Получение скрипта .jl для автоматического создания модели Engee
⚠️ When converting models, it is worth considering that in the current release of Engee:
engee.version()
conversion is supported for blocks supported by the code generator.
Conversion support for all blocks in the Engee library will also be implemented in the future.
The content of the resulting script is given below. The following functions are used in the code to automatically build the .engee model:
engee.create()- creates a new model;engee.add_block()- adds a block from Engee library;engee.set_param!()- updates model/block parameters;engee.delete_contents()- deletes system contents;engee.add_line()- adds communication/links between blocks;engee.save()- saves the model;engee.close()- closes the model.
Additionally, the programme control can be found in related example.
Resulting code
model = engee.create("alphabetafilter")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/processNoiseVariance")
engee.set_param!("alphabetafilter/processNoiseVariance", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/measurementNoiseVariance")
engee.set_param!("alphabetafilter/measurementNoiseVariance", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/y")
engee.set_param!("alphabetafilter/y", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/Subsystem", "alphabetafilter/determineAlphaBeta")
engee.delete_contents("alphabetafilter/determineAlphaBeta")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/determineAlphaBeta/processNoiseVariance")
engee.set_param!("alphabetafilter/determineAlphaBeta/processNoiseVariance", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/determineAlphaBeta/measurementNoiseVariance")
engee.set_param!("alphabetafilter/determineAlphaBeta/measurementNoiseVariance", "SignalType"=>"real")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/determineAlphaBeta/Add")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/determineAlphaBeta/Add1")
engee.set_param!("alphabetafilter/determineAlphaBeta/Add1", "Inputs"=>"+-")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/determineAlphaBeta/Add2")
engee.set_param!("alphabetafilter/determineAlphaBeta/Add2", "Inputs"=>"+-")
engee.add_block("/Basic/Math Operations/Bias", "alphabetafilter/determineAlphaBeta/Bias")
engee.set_param!("alphabetafilter/determineAlphaBeta/Bias", "Bias"=>"4")
engee.add_block("/Basic/Math Operations/Bias", "alphabetafilter/determineAlphaBeta/Bias1")
engee.set_param!("alphabetafilter/determineAlphaBeta/Bias1", "Bias"=>"-1")
engee.add_block("/Basic/Math Operations/Bias", "alphabetafilter/determineAlphaBeta/Bias2")
engee.set_param!("alphabetafilter/determineAlphaBeta/Bias2", "Bias"=>"-2")
engee.add_block("/Basic/Math Operations/Bias", "alphabetafilter/determineAlphaBeta/Bias3")
engee.set_param!("alphabetafilter/determineAlphaBeta/Bias3", "Bias"=>"-1")
engee.add_block("/Basic/Sources/Constant", "alphabetafilter/determineAlphaBeta/Constant")
engee.set_param!("alphabetafilter/determineAlphaBeta/Constant", "Value"=>"dt")
engee.set_param!("alphabetafilter/determineAlphaBeta/Constant", "SampleTime"=>"0.0")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain", "Gain"=>"8")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain1")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain1", "Gain"=>"1/4")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain2")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain2", "Gain"=>"-1")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain3")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain3", "Gain"=>"-1")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain4")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain4", "Gain"=>"2")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain5")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain5", "Gain"=>"-1")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/determineAlphaBeta/Gain6")
engee.set_param!("alphabetafilter/determineAlphaBeta/Gain6", "Gain"=>"4")
engee.add_block("/Basic/Math Operations/Math Function", "alphabetafilter/determineAlphaBeta/Math Function")
engee.set_param!("alphabetafilter/determineAlphaBeta/Math Function", "Operator"=>"square")
engee.add_block("/Basic/Math Operations/Math Function", "alphabetafilter/determineAlphaBeta/Math Function1")
engee.set_param!("alphabetafilter/determineAlphaBeta/Math Function1", "Operator"=>"square")
engee.add_block("/Basic/Math Operations/Math Function", "alphabetafilter/determineAlphaBeta/Math Function2")
engee.set_param!("alphabetafilter/determineAlphaBeta/Math Function2", "Operator"=>"square")
engee.add_block("/Basic/Math Operations/Math Function", "alphabetafilter/determineAlphaBeta/Math Function3")
engee.set_param!("alphabetafilter/determineAlphaBeta/Math Function3", "Operator"=>"reciprocal")
engee.add_block("/Basic/Math Operations/Product", "alphabetafilter/determineAlphaBeta/Product")
engee.set_param!("alphabetafilter/determineAlphaBeta/Product", "Inputs"=>"**")
engee.add_block("/Basic/Math Operations/Product", "alphabetafilter/determineAlphaBeta/Product1")
engee.set_param!("alphabetafilter/determineAlphaBeta/Product1", "Inputs"=>"**")
engee.add_block("/Basic/Math Operations/Sqrt", "alphabetafilter/determineAlphaBeta/Sqrt")
engee.add_block("/Basic/Math Operations/Sqrt", "alphabetafilter/determineAlphaBeta/Sqrt1")
engee.add_block("/Basic/Ports & Subsystems/Out1", "alphabetafilter/determineAlphaBeta/alpha")
engee.set_param!("alphabetafilter/determineAlphaBeta/alpha", "InitialOutput"=>"0")
engee.add_block("/Basic/Ports & Subsystems/Out1", "alphabetafilter/determineAlphaBeta/beta")
engee.set_param!("alphabetafilter/determineAlphaBeta/beta", "InitialOutput"=>"0")
engee.add_line("alphabetafilter/determineAlphaBeta", "Add/1", "Sqrt/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Math Function2/1", "Bias1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain1/1", "Math Function2/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Sqrt/1", "Add1/2")
engee.add_line("alphabetafilter/determineAlphaBeta", "Bias/1", "Add1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Bias1/1", "Gain2/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Add2/1", "beta/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Math Function/1", "Product/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain5/1", "Sqrt1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Add1/1", "Gain1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Bias3/1", "Gain5/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Product1/1", "Math Function1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Product1/1", "Gain/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Product1/1", "Bias/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Bias2/1", "Gain3/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Product/1", "Product1/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain/1", "Add/2")
engee.add_line("alphabetafilter/determineAlphaBeta", "Sqrt1/1", "Gain6/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain2/1", "alpha/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain2/1", "Bias2/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain2/1", "Bias3/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain3/1", "Gain4/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain6/1", "Add2/2")
engee.add_line("alphabetafilter/determineAlphaBeta", "Math Function1/1", "Add/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Gain4/1", "Add2/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "Constant/1", "Math Function/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "measurementNoiseVariance/1", "Math Function3/1")
engee.add_line("alphabetafilter/determineAlphaBeta", "processNoiseVariance/1", "Product/2")
engee.add_line("alphabetafilter/determineAlphaBeta", "Math Function3/1", "Product1/2")
engee.add_block("/Basic/Ports & Subsystems/Subsystem", "alphabetafilter/filterInputSignal")
engee.delete_contents("alphabetafilter/filterInputSignal")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/filterInputSignal/alpha")
engee.set_param!("alphabetafilter/filterInputSignal/alpha", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/filterInputSignal/beta")
engee.set_param!("alphabetafilter/filterInputSignal/beta", "SignalType"=>"real")
engee.add_block("/Basic/Ports & Subsystems/In1", "alphabetafilter/filterInputSignal/y")
engee.set_param!("alphabetafilter/filterInputSignal/y", "SignalType"=>"real")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/filterInputSignal/Add")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/filterInputSignal/Add1")
engee.set_param!("alphabetafilter/filterInputSignal/Add1", "Inputs"=>"+-")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/filterInputSignal/Add2")
engee.add_block("/Basic/Math Operations/Add", "alphabetafilter/filterInputSignal/Add3")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/filterInputSignal/Gain")
engee.set_param!("alphabetafilter/filterInputSignal/Gain", "Gain"=>"dt")
engee.add_block("/Basic/Math Operations/Gain", "alphabetafilter/filterInputSignal/Gain1")
engee.set_param!("alphabetafilter/filterInputSignal/Gain1", "Gain"=>"1/dt")
engee.add_block("/Basic/Math Operations/Product", "alphabetafilter/filterInputSignal/Product")
engee.set_param!("alphabetafilter/filterInputSignal/Product", "Inputs"=>"**")
engee.add_block("/Basic/Math Operations/Product", "alphabetafilter/filterInputSignal/Product1")
engee.set_param!("alphabetafilter/filterInputSignal/Product1", "Inputs"=>"**")
engee.add_block("/Basic/Discrete/Unit Delay", "alphabetafilter/filterInputSignal/Unit Delay")
engee.set_param!("alphabetafilter/filterInputSignal/Unit Delay", "InitialCondition"=>"0")
engee.set_param!("alphabetafilter/filterInputSignal/Unit Delay", "SampleTime"=>"-1")
engee.add_block("/Basic/Discrete/Unit Delay", "alphabetafilter/filterInputSignal/Unit Delay1")
engee.set_param!("alphabetafilter/filterInputSignal/Unit Delay1", "InitialCondition"=>"0")
engee.set_param!("alphabetafilter/filterInputSignal/Unit Delay1", "SampleTime"=>"-1")
engee.add_block("/Basic/Ports & Subsystems/Out1", "alphabetafilter/filterInputSignal/x")
engee.set_param!("alphabetafilter/filterInputSignal/x", "InitialOutput"=>"0")
engee.add_line("alphabetafilter/filterInputSignal", "Add1/1", "Product/2")
engee.add_line("alphabetafilter/filterInputSignal", "Add1/1", "Product1/1")
engee.add_line("alphabetafilter/filterInputSignal", "Unit Delay/1", "Add/1")
engee.add_line("alphabetafilter/filterInputSignal", "Add3/1", "Unit Delay1/1")
engee.add_line("alphabetafilter/filterInputSignal", "Unit Delay1/1", "Add3/2")
engee.add_line("alphabetafilter/filterInputSignal", "Unit Delay1/1", "Gain/1")
engee.add_line("alphabetafilter/filterInputSignal", "y/1", "Add1/1")
engee.add_line("alphabetafilter/filterInputSignal", "Product/1", "Add2/2")
engee.add_line("alphabetafilter/filterInputSignal", "Add2/1", "Unit Delay/1")
engee.add_line("alphabetafilter/filterInputSignal", "Add2/1", "x/1")
engee.add_line("alphabetafilter/filterInputSignal", "Gain1/1", "Add3/1")
engee.add_line("alphabetafilter/filterInputSignal", "Product1/1", "Gain1/1")
engee.add_line("alphabetafilter/filterInputSignal", "alpha/1", "Product/1")
engee.add_line("alphabetafilter/filterInputSignal", "Gain/1", "Add/2")
engee.add_line("alphabetafilter/filterInputSignal", "Add/1", "Add2/1")
engee.add_line("alphabetafilter/filterInputSignal", "Add/1", "Add1/2")
engee.add_line("alphabetafilter/filterInputSignal", "beta/1", "Product1/2")
engee.add_block("/Basic/Ports & Subsystems/Out1", "alphabetafilter/x")
engee.set_param!("alphabetafilter/x", "InitialOutput"=>"0")
engee.add_line("alphabetafilter", "measurementNoiseVariance/1", "determineAlphaBeta/2")
engee.add_line("alphabetafilter", "processNoiseVariance/1", "determineAlphaBeta/1")
engee.add_line("alphabetafilter", "determineAlphaBeta/1", "filterInputSignal/1")
engee.add_line("alphabetafilter", "determineAlphaBeta/2", "filterInputSignal/2")
engee.add_line("alphabetafilter", "y/1", "filterInputSignal/3")
engee.add_line("alphabetafilter", "filterInputSignal/1", "x/1")
engee.save(model, "alphabetafilter.engee")
engee.close()
end
Building the Engee model
The second and final step of transferring the Simulink model to the Engee model is to reproduce the resulting script:
cd(joinpath(путь_примера,"engee")) # Переходим в папку примера – в ней будет располагаться модель .engee
include(скриптJL) # Запускаем выполнение скрипта .jl для автоматического создания модели .engee
As a result of the previous cell, the required model file will appear in the folder of the current example .engee. The obtained model is ready for use.
Using the engee.load() and engee.open() functions of the programme control we can make sure that the built model with subsystems are opened and therefore they are created correctly.
# Функция для проверки модели и подсистем на открытие
function model_check(name::String, path::String, subsystem::String, delay::Int) # Функция получает имя и путь модели, имя подсистемы, время ожидания
model = engee.load(path; sync_gui=true) # Загружаем модель
sleep(delay) # Ожидаем завершения синхронизации GUI
engee.open(joinpath(name,subsystem); sync_gui=true) # Открываем подсистему
sleep(delay) # Ожидаем завершения синхронизации GUI
engee.save(model, name*".engee"; force=true, sync_gui=true) # Обновляем модель
sleep(delay) # Ожидаем завершения синхронизации GUI
# Закрываем модель
try
engee.close(model;sync_gui=true)
catch e
engee.close_all(;sync_gui=true)
end
sleep(delay) # Ожидаем завершения синхронизации GUI
return nothing # Функция ничего не возвращает
end;
Let's check if the model and subsystems are created - let's open the subsystems one by one. It takes some time for the cell execution to synchronise the graphical user interface (GUI).
подсистемы = ["filterInputSignal", "determineAlphaBeta"] # Вектор с именами открываемых подсистем
for i in подсистемы # Цикл по вектору
model_check(имя_модели, модельENGEE, i, 5) # Циклический вызов функции проверки модели и подсистем
# время ожидания = 3 [сек]. При медленной синхронизации GUI
# рекомендуется увеличить время ожидания
end
During cell execution, the model and subsystem tabs are opened and closed in the Engee modelling environment. You must wait for all model and GUI control commands to complete.
Next, let's proceed to verification of the automatically built model .engee.
Verification of the obtained model
Let's use the method described in the example C code integration.
Simulation of the original Simulink model
The initial data for verification are the results of the Simulink model. To obtain the results, let's generate code from it using Simulink Embedded Coder.
# Загружаем и устанавливаем библиотеку MATLAB, если она ещё не установлена
import Pkg; Pkg.add("MATLAB")
cgModel = модельSLX # Путь модели Simulink
cgPath = mkdir(joinpath(путь_примера,"slx","cg")); # Создаём директорию для результатов генерации кода из модели Simulink
using MATLAB # Подключаем библиотеку MATLAB
mat""" % Вставка кода на языке MATLAB
model = load_system($cgModel); % Загружаем модель Simulink в память
path = $cgPath; % Сохраняем путь к папке генерации кода
set_param(0, 'CacheFolder', path) % Определяем путь к папке кэша моделирования
set_param(0, 'CodeGenFolder', path) % Определяем путь к папке генерации кода
slbuild(model) % Генерируем код из исходной модели Simulink
set_param(0, 'CacheFolder', '') % Сбрасываем путь к папке кэша моделирования
set_param(0, 'CodeGenFolder', '') % Сбрасываем путь к папке генерации кода
""" # Конец вставки кода
The model in which the generated code from the Simulink model is embedded using the C Function block is test_cg_simulink.engee. The necessary paths to the C files are already specified in the block C Function.
Let's load, execute and close this model.
тест_моделиSLX = engee.load(joinpath(путь_примера, "test_cg_simulink.engee"), force = true); # Загружаем модель для сбора исходных данных верификации
результаты_моделиSLX = engee.run(тест_моделиSLX); # Выполняем модель и сохраняем исходные данные
engee.close(тест_моделиSLX, force = true);
For clarity, let's plot the signals obtained as a result of modelling:
# Передадим результаты моделирования в отдельные переменные:
noisy_t_slx = результаты_моделиSLX["noisy"].time;
noisy_v_slx = результаты_моделиSLX["noisy"].value;
filtered_t_slx = результаты_моделиSLX["filtered"].time;
filtered_v_slx = результаты_моделиSLX["filtered"].value;
plot(noisy_t_slx, noisy_v_slx, label = "Исходный сигнал") # Строим график зашумленного сигнала
plot!(filtered_t_slx, filtered_v_slx, label = "Отфильтрованный сигнал") # Строим график отфильтрованного сигнала (при помощи кода, сгенерированного из модели Simulink)
plot!(legend = :bottomright) # Расположение легенды
title!("Результаты работы исходной модели Simulink") # Заголовок
xlabel!("Время, [с]") # Подпись оси X
ylabel!("Амплитуда") # Подпись оси Y
Modelling of the obtained Engee model
Now let's move on to modelling and obtaining results from the Engee model built using an automatically generated script.
Define the adjustable parameters of the model (the same values as in the Simulink model):
# Настраиваемые параметры модели:
dt = 0.01;
Gain = 1/dt;
Generate C code from the automatically built Engee model:
engee.generate_code(модельENGEE, путь_примера*"/engee/code") # Автоматическая генерация кода Си из модели Engee
Identical to the previous model, which also uses a block to
C Function the generated code from the Engee model is embedded - test_cg_engee.engee. The necessary paths to the C files are already specified in the block C Function.
Let's load, execute and close this model.
тест_моделиENGEE = engee.load(joinpath(путь_примера, "test_cg_engee.engee"), force = true); # Загружаем модель для сбора данных верификации модели Engee
результаты_моделиENGEE = engee.run(тест_моделиENGEE); # Выполняем модель и сохраняем исходные данные
engee.close(тест_моделиENGEE, force = true);
For clarity, let's plot the signals obtained as a result of modelling:
# Передадим результаты моделирования в отдельные переменные:
noisy_t_engee = результаты_моделиENGEE["noisy"].time;
noisy_v_engee = результаты_моделиENGEE["noisy"].value;
filtered_t_engee = результаты_моделиENGEE["filtered"].time;
filtered_v_engee = результаты_моделиENGEE["filtered"].value;
plot(noisy_t_engee, noisy_v_engee, label = "Исходный сигнал") # Строим график зашумленного сигнала
plot!(filtered_t_engee, filtered_v_engee, label = "Отфильтрованный сигнал") # Строим график отфильтрованного сигнала (при помощи кода, сгенерированного из модели Engee)
plot!(legend = :bottomright) # Расположение легенды
title!("Результаты работы построенной модели Engee") # Заголовок
xlabel!("Время, [с]") # Подпись оси X
ylabel!("Амплитуда") # Подпись оси Y
Comparison of modelling results
The graphs allow us to confidently conclude that the results of model building in the two modelling environments under consideration are similar. Let us calculate the minimum and maximum values of absolute deviations of the simulation results for the Simulink model and the Engee model converted from it:
# Вычисляем абсолютные отклонения величин смоделированных сигналов
Δ_noisy_v = noisy_v_slx .- noisy_v_engee;
Δ_filtered_v = filtered_v_slx .- filtered_v_engee;
println("""Абсолютные отклонения величин сигналов для построенной модели Engee от исходной модели Simulink:
- зашумленный сигнал минимум: $(minimum(Δ_noisy_v)) максимум: $(maximum(Δ_noisy_v))
- отфильтрованный сигнал минимум: $(minimum(Δ_filtered_v)) максимум: $(maximum(Δ_filtered_v))""")
As can be seen from the graphical and analytical comparison, there are no deviations in the performance of the automatically generated Engee model compared to the performance of the original Simulink model.
Conclusions
In this example, we have examined the process of converting Simulink models to Engee models, verified the built model using automatic code generation. We also examined in detail the application of many of Engee's software control features. At the end of this experiment, we found that the converted Engee model is not different from the original Simulink model. The original model can be deleted as unnecessary and to avoid confusion:
# rm(модельSLX; force=true); # При необходимости раскомментируйте строку