добавлен экспорт и импорт настроек оболочки и приложения МК

работа с конфигом (чтение запись) перенесена в отдельный файл

добавлена возможность через popup сделать define = чему-то
This commit is contained in:
Razvalyaev 2025-06-17 18:34:23 +03:00
parent e77a659710
commit 5648875cd2
9 changed files with 492 additions and 163 deletions

Binary file not shown.

Binary file not shown.

View File

@ -75,14 +75,14 @@ classdef appWrap
errordlg('Файл не найден');
end
end
end
%% SPECIFIC TOOLS
methods(Static, Access = private)
function [filename, section, tool, example] = getAppWrapperUserFile(block)
sel = get_param(block, 'appWrapperFunc');
function [filename, section, tool, example] = getAppWrapperUserFile(block, sel)
if (nargin < 2)
sel = get_param(block, 'appWrapperFunc');
end
basePath = mcuPath.get('appWrapperPath');
if isempty(basePath)
errordlg('Не указан путь к файлам обёртки (wrapperPath).');
@ -150,7 +150,5 @@ classdef appWrap
end
end
end
end

143
McuLib/m/configJs.m Normal file
View File

@ -0,0 +1,143 @@
classdef configJs
methods(Static)
function config = update(blockPath, config)
if isempty(config)
return;
end
mask = Simulink.Mask.get(blockPath);
maskParams = mask.Parameters;
paramNames = arrayfun(@(p) p.Name, maskParams, 'UniformOutput', false);
% Обработка остальных секций (с дефайнами)
periphs = fieldnames(config);
for i = 1:numel(periphs)
periph = periphs{i};
% Проверяем есть ли Defines
if ~isfield(config.(periph), 'Defines')
continue;
end
defines = config.(periph).Defines;
defNames = fieldnames(defines);
for j = 1:numel(defNames)
defPrompt = defNames{j};
paramName = matlab.lang.makeValidName(defPrompt);
% Проверка, существует ли параметр с таким именем
if ismember(paramName, paramNames)
param = mask.getParameter(paramName);
valStr = param.Value;
% Проверяем, существует ли элемент defPrompt в структуре defines
if isfield(defines, defPrompt)
% Преобразуем строку в соответствующий тип
if strcmpi(defines.(defPrompt).Type, 'checkbox')
config.(periph).Defines.(defPrompt).Default = strcmpi(valStr, 'on');
elseif strcmpi(defines.(defPrompt).Type, 'edit')
valNum = str2double(valStr);
if isnan(valNum)
config.(periph).Defines.(defPrompt).Default = valStr;
else
config.(periph).Defines.(defPrompt).Default = valNum;
end
end
end
end
end
end
end
function config = read(blockPath)
mask = Simulink.Mask.get(blockPath);
pathparam = mask.getParameter('periphPath');
config_path = pathparam.Value;
if ~isempty(config_path)
jsonText = fileread(config_path);
config = jsondecode(jsonText);
else
config = [];
end
end
function write(config)
if isempty(config)
return
end
blockHandle = gcbh;
mask = Simulink.Mask.get(blockHandle);
pathparam = mask.getParameter('periphPath');
config_path = pathparam.Value;
jsonText = jsonencode(config, 'PrettyPrint', true);
fid = fopen(config_path, 'w', 'n', 'UTF-8');
if fid == -1
error('Не удалось открыть файл periph_config.json для записи.');
end
fwrite(fid, jsonText, 'char');
fclose(fid);
end
function value = get_field(configStruct, targetConfig)
% получить targetConfig структуру из конфига (для глубоко вложенных)
value = [];
fields = fieldnames(configStruct);
for i = 1:numel(fields)
key = fields{i};
if strcmp(key, targetConfig)
value = configStruct.(key);
return; % нашли и возвращаем
elseif isstruct(configStruct.(key))
value = configJs.get_field(configStruct.(key), targetConfig);
if ~isempty(value)
return; % нашли во вложенной структуре
end
end
end
% Если не нашли, можно выбросить ошибку или вернуть пустое
if isempty(value)
% error('Поле "%s" не найдено в структуре.', targetConfig);
end
end
function short = get_final_name_from_prefix(prefix)
% Берёт последнее имя после "_" (читаемое имя)
parts = strsplit(prefix, '_');
short = parts{end};
end
function value = convert_code_value(codeField)
% Преобразует значение поля Options в строку
if ischar(codeField)
value = codeField;
elseif isstring(codeField)
value = char(codeField);
elseif iscell(codeField)
value = strjoin(codeField, newline);
else
% warning('Неподдерживаемый тип данных: сохранено как пустая строка');
value = '';
end
end
end
methods(Static, Access=private)
end
end

274
McuLib/m/mainConfig.m Normal file
View File

@ -0,0 +1,274 @@
classdef mainConfig
methods(Static)
function config = export()
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
wrapParamToExport = {'wrapperPath', 'enableDebug', 'mcuClk', ...
'threadCycles', 'enableThreading', 'enableDeinit'};
portParamToExport = {'inNumb', ...
'in_port_1_name', 'in_port_1_width', ...
'in_port_2_name', 'in_port_2_width', ...
'in_port_3_name', 'in_port_3_width', ...
'in_port_4_name', 'in_port_4_width', ...
'in_port_5_name', 'in_port_5_width', ...
'outNumb', ...
'out_port_1_name', 'out_port_1_width', ...
'out_port_2_name', 'out_port_2_width', ...
'out_port_3_name', 'out_port_3_width', ...
'out_port_4_name', 'out_port_4_width', ...
'out_port_5_name', 'out_port_5_width',};
appParamToExport = {'appWrapperPath', 'srcTable', 'incTable', 'userDefs'};
config = struct();
for i = 1:numel(wrapParamToExport)
paramName = wrapParamToExport{i};
def = mainConfig.exportParamToConfig(mask, paramName);
config.(paramName) = def;
end
for i = 1:numel(portParamToExport)
paramName = portParamToExport{i};
def = mainConfig.exportParamToConfig(mask, paramName);
config.(paramName) = def;
end
for i = 1:numel(appParamToExport)
paramName = appParamToExport{i};
def = mainConfig.exportParamToConfig(mask, paramName);
config.(paramName) = def;
end
jsonStr = jsonencode(config, 'PrettyPrint', true); % 'PrettyPrint' для форматирования
% Диалог сохранения файла
[file, path] = uiputfile('*.json', 'Сохранить конфигурацию как', 'WrapperConfig.json');
if isequal(file, 0) || isequal(path, 0)
disp('Сохранение отменено пользователем.');
return;
end
filepath = fullfile(path, file);
% Сохраняем в файл
fid = fopen(filepath, 'w');
if fid == -1
mcuMask.disp(0, 'Не удалось открыть файл для записи: %s', filename);
end
fwrite(fid, jsonStr, 'char');
fclose(fid);
end
function import()
% Получаем путь к текущему блоку
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
% Выбор JSON-файла через диалоговое окно
[file, path] = uigetfile('*.json', 'Выберите файл конфигурации');
if isequal(file, 0)
mcuMask.disp(0, 'Импорт отменён пользователем.');
return;
end
fullpath = fullfile(path, file);
% Чтение и декодирование JSON
try
jsonStr = fileread(fullpath);
config = jsondecode(jsonStr);
catch err
mcuMask.disp(0, 'Ошибка при чтении или разборе JSON: %s', err.message);
end
% Применение параметров из конфигурации
paramNames = fieldnames(config);
for i = 1:numel(paramNames)
paramName = paramNames{i};
def = config.(paramName);
try
mainConfig.applyDefToMask(mask, paramName, def);
catch err
mcuMask.disp(0, 'Ошибка при применении параметра "%s": %s', paramName, err.message);
end
end
mcuMask.disp(0, 'Конфигурация успешно импортирована.');
end
end
methods(Static, Access=private)
function def = exportParamToConfig(mask, paramName)
% mask объект Simulink.Mask.get(blockPath)
% paramName имя параметра (как в mask.Parameters.Name)
param = mask.getParameter(paramName);
if isempty(param)
mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName);
def = [];
return;
end
def = struct();
% Prompt
def.Prompt = param.Prompt;
% Тип параметра
def.Type = param.Type;
% Значение по умолчанию
val = param.Value;
switch lower(param.Type)
case 'checkbox'
def.Default = strcmp(val, 'on');
case {'edit', 'spinbox'}
num = str2double(val);
if ~isnan(num)
def.Default = num;
else
def.Default = val;
end
case 'customtable'
def.Default = customtable.parse(param.Name); % или можно попытаться распарсить значение позже
case 'text'
def.Default = ''; % или можно попытаться распарсить значение позже
otherwise
def.Default = val;
end
% Alias, если есть
if ~isempty(param.Alias)
def.Def = param.Alias;
end
% % Row (new/current)
% try
% rowSetting = param.DialogControl.Row;
% def.NewRow = strcmp(rowSetting, 'new');
% catch
% def.NewRow = false;
% end
end
function applyDefToMask(mask, paramName, def)
% mask объект Simulink.Mask.get(blockPath)
% paramName имя параметра маски
% def структура, полученная из extractDefFromMask
% Получаем параметр
param = mask.getParameter(paramName);
if isempty(param)
mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName);
return;
end
if isempty(def)
mcuMask.disp(0, 'Параметр "%s" не найден в конфиге.', paramName);
return;
end
switch lower(def.Type)
case 'checkbox'
% Логическое значение true/false
if islogical(def.Default)
param.Value = mainConfig.ternary(def.Default, 'on', 'off');
else
mcuMask.disp(0, 'Ожидалось логическое значение для checkbox "%s".', paramName);
end
case {'edit', 'spinbox'}
% Строка или число
if isnumeric(def.Default)
param.Value = num2str(def.Default);
elseif ischar(def.Default) || isstring(def.Default)
param.Value = char(def.Default);
else
mcuMask.disp(0, 'Некорректный формат значения для edit "%s".', paramName);
end
case 'customtable'
% Массив строк
if iscell(def.Default)
customtable.collect(paramName, def.Default);
else
mcuMask.disp(0, 'customtable "%s" требует cell-массив строк.', paramName);
end
case 'text'
% Просто текстовая строка
if ischar(def.Default) || isstring(def.Default)
param.Value = char(def.Default);
else
mcuMask.disp(0, 'text-параметр "%s" должен быть строкой.', paramName);
end
case 'popup'
% popup установить значение, если оно есть в списке
if ischar(def.Default) && isfield(def, 'Def') && any(strcmp(def.Default, def.Def))
param.Value = def.Default;
else
mcuMask.disp(0, 'popup-параметр "%s" имеет неверное значение или список.', paramName);
end
otherwise
% По умолчанию просто устанавливаем строковое значение
if ischar(def.Default) || isstring(def.Default)
param.Value = char(def.Default);
elseif isnumeric(def.Default)
param.Value = num2str(def.Default);
else
mcuMask.disp(0, 'Неизвестный формат значения параметра "%s".', paramName);
end
end
% Применение Prompt, если нужно
if isfield(def, 'Prompt')
param.Prompt = def.Prompt;
end
% Применение Alias, если есть
if isfield(def, 'Def') && ischar(def.Def)
param.Alias = def.Def;
end
% % Установка Row, если поддерживается
% if isfield(def, 'NewRow')
% try
% if def.NewRow
% param.DialogControl.Row = 'new';
% else
% param.DialogControl.Row = 'current';
% end
% catch
% % Некоторым типам параметров Row может быть неприменим
% end
% end
end
function result = ternary(cond, a, b)
if cond
result = a;
else
result = b;
end
end
end
end

View File

@ -8,7 +8,7 @@ classdef mcuPath
end
%% ADD PATH TO TABLE
function addSourceFileTable(targetParamName, message)
% Открываем проводник для выбора файлов
[files, pathstr] = uigetfile({ ...

View File

@ -66,9 +66,9 @@ function res = mexing(compile_mode)
beep
else
blockPath = gcb;
config = periphConfig.read_config(blockPath);
config = periphConfig.update_config(blockPath, config);
periphConfig.write_config(config);
config = configJs.read(blockPath);
config = configJs.update(blockPath, config);
configJs.write(config);
periphConfig.updateMask(blockPath, config);
end
end
@ -232,10 +232,16 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_
end
% Берём alias из маски
val = '';
if ~strcmp(param.Type, 'popup')
def_name = param.Alias;
else
def_name = param.Value;
if strcmp(param.Alias, '')
def_name = param.Value;
else
def_name = param.Alias;
val = param.Value;
end
end
if strcmp(def_name, '')
@ -259,7 +265,7 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_
newDefine = '';
end
else
newDefine = ['-D"' def_name '"'];
newDefine = ['-D"' def_name '__EQ__' val '"'];
end

View File

@ -74,7 +74,7 @@ classdef periphConfig
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
config = periphConfig.read_config(blockPath);
config = configJs.read(blockPath);
containerName = 'configTabAll';
container = mask.getDialogControl(containerName);
paramsAll = mcuMask.collect_all_parameters(container);
@ -130,7 +130,7 @@ classdef periphConfig
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
hObj = mask.getParameter(paramName);
config = periphConfig.read_config(blockPath);
config = configJs.read(blockPath);
container = mask.getDialogControl('configTabAll');
% === Проверка на Tab_<Periph>_Enable ===
@ -175,7 +175,7 @@ classdef periphConfig
% Получаем содержимое config по nameBase возможно, вложенное
try
valueStruct = periphConfig.get_field(config, nameBase);
valueStruct = configJs.get_field(config, nameBase);
catch
warning('Не удалось найти путь %s в config.', nameBase);
return;
@ -209,97 +209,6 @@ classdef periphConfig
periphConfig.addCodeBat(CodeStruct, periphPath);
end
function config = update_config(blockPath, config)
if isempty(config)
return;
end
mask = Simulink.Mask.get(blockPath);
maskParams = mask.Parameters;
paramNames = arrayfun(@(p) p.Name, maskParams, 'UniformOutput', false);
% Обработка остальных секций (с дефайнами)
periphs = fieldnames(config);
for i = 1:numel(periphs)
periph = periphs{i};
% Пропускаем Code и UserCode
if strcmp(periph, 'Code') || strcmp(periph, 'UserCode')
continue;
end
% Проверяем есть ли Defines
if ~isfield(config.(periph), 'Defines')
continue;
end
defines = config.(periph).Defines;
defNames = fieldnames(defines);
for j = 1:numel(defNames)
defPrompt = defNames{j};
paramName = matlab.lang.makeValidName(defPrompt);
% Проверка, существует ли параметр с таким именем
if ismember(paramName, paramNames)
param = mask.getParameter(paramName);
valStr = param.Value;
% Проверяем, существует ли элемент defPrompt в структуре defines
if isfield(defines, defPrompt)
% Преобразуем строку в соответствующий тип
if strcmpi(defines.(defPrompt).Type, 'checkbox')
config.(periph).Defines.(defPrompt).Default = strcmpi(valStr, 'on');
elseif strcmpi(defines.(defPrompt).Type, 'edit')
valNum = str2double(valStr);
if isnan(valNum)
config.(periph).Defines.(defPrompt).Default = valStr;
else
config.(periph).Defines.(defPrompt).Default = valNum;
end
end
end
end
end
end
end
function config = read_config(blockPath)
mask = Simulink.Mask.get(blockPath);
pathparam = mask.getParameter('periphPath');
config_path = pathparam.Value;
if ~isempty(config_path)
jsonText = fileread(config_path);
config = jsondecode(jsonText);
else
config = [];
end
end
function write_config(config)
if isempty(config)
return
end
blockHandle = gcbh;
mask = Simulink.Mask.get(blockHandle);
pathparam = mask.getParameter('periphPath');
config_path = pathparam.Value;
jsonText = jsonencode(config, 'PrettyPrint', true);
fid = fopen(config_path, 'w', 'n', 'UTF-8');
if fid == -1
error('Не удалось открыть файл periph_config.json для записи.');
end
fwrite(fid, jsonText, 'char');
fclose(fid);
end
end
@ -347,7 +256,7 @@ classdef periphConfig
end
else
if strcmp(key, 'Sources') || strcmp(key, 'Includes')
baseName = periphConfig.get_final_name_from_prefix(prefix);
baseName = configJs.get_final_name_from_prefix(prefix);
periphConfig.clear_single_periph_code_param(mask, baseName);
end
end
@ -375,7 +284,7 @@ classdef periphConfig
end
else
if strcmp(key, 'Sources') || strcmp(key, 'Includes')
baseName = periphConfig.get_final_name_from_prefix(prefix);
baseName = configJs.get_final_name_from_prefix(prefix);
periphConfig.store_single_periph_code(mask, baseName, configStruct);
end
end
@ -534,10 +443,6 @@ classdef periphConfig
mcuMask.delete_all_tabs(mask, container);
end
function res = addCodeBat(codeConfig, codePath)
% Добавить сурсы и пути в батник
@ -668,6 +573,9 @@ classdef periphConfig
if isfield(def, 'Def') && iscell(def.Def) && ~isempty(def.Def)
choices = def.Def;
valStr = ''; % по умолчанию ничего
elseif isfield(def, 'Options')
choices = def.Def;
valStr = ''; % по умолчанию ничего
else
warning('Popout параметр "%s" не содержит допустимого списка в Def.', defPrompt);
return;
@ -713,7 +621,12 @@ classdef periphConfig
if isfield(def, 'Def')
if strcmp(paramType, 'popup')
param.TypeOptions = def.Def;
if iscell(def.Def)
param.TypeOptions = def.Def;
elseif isfield(def, 'Options')
param.Alias = def.Def;
param.TypeOptions = def.Options;
end
else
param.Alias = def.Def;
end
@ -752,7 +665,7 @@ classdef periphConfig
paramName = ['Hidden_' char(periph) '_Sources'];
try
param = mask.getParameter(paramName);
param.Value = periphConfig.convert_code_value(code.Sources);
param.Value = configJs.convert_code_value(code.Sources);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
@ -763,57 +676,15 @@ classdef periphConfig
paramName = ['Hidden_' char(periph) '_Includes'];
try
param = mask.getParameter(paramName);
param.Value = periphConfig.convert_code_value(code.Includes);
param.Value = configJs.convert_code_value(code.Includes);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
end
end
function value = get_field(configStruct, targetConfig)
% получить targetConfig структуру из конфига (для глубоко вложенных)
value = [];
fields = fieldnames(configStruct);
for i = 1:numel(fields)
key = fields{i};
if strcmp(key, targetConfig)
value = configStruct.(key);
return; % нашли и возвращаем
elseif isstruct(configStruct.(key))
value = periphConfig.get_field(configStruct.(key), targetConfig);
if ~isempty(value)
return; % нашли во вложенной структуре
end
end
end
% Если не нашли, можно выбросить ошибку или вернуть пустое
if isempty(value)
% error('Поле "%s" не найдено в структуре.', targetConfig);
end
end
function short = get_final_name_from_prefix(prefix)
% Берёт последнее имя после "_" (читаемое имя)
parts = strsplit(prefix, '_');
short = parts{end};
end
function value = convert_code_value(codeField)
% Преобразует значение поля Options в строку
if ischar(codeField)
value = codeField;
elseif isstring(codeField)
value = char(codeField);
elseif iscell(codeField)
value = strjoin(codeField, newline);
else
% warning('Неподдерживаемый тип данных: сохранено как пустая строка');
value = '';
end
end
function res = ternary(cond, valTrue, valFalse)
if cond
res = valTrue;

View File

@ -1,5 +1,5 @@
<deployment-project plugin="plugin.toolbox" plugin-version="1.0">
<configuration build-checksum="2658716694" file="E:\.WORK\MATLAB\mcu_matlab\mcuwrapper.prj" location="E:\.WORK\MATLAB\mcu_matlab" name="mcuwrapper" target="target.toolbox" target-name="Package Toolbox">
<configuration build-checksum="2658716694" file="F:\Work\Projects\MATLAB\mcu_matlab_lib\mcuwrapper.prj" location="F:\Work\Projects\MATLAB\mcu_matlab_lib" name="mcuwrapper" target="target.toolbox" target-name="Package Toolbox">
<param.appname>MCU Wrapper</param.appname>
<param.authnamewatermark>Razvalyaev</param.authnamewatermark>
<param.email>wot890089@mail.ru</param.email>
@ -27,7 +27,7 @@
<param.exported.on.package>false</param.exported.on.package>
<param.required.addons>
<requiredaddons>
<requiredAddOn earliest="earliest" fromRepository="true" id="e5534541-4a80-11e4-9553-005056977bd0" include="true" latest="latest">findjobj - find java handles of Matlab graphic objects</requiredAddOn>
<requiredAddOn earliest="earliest" fromRepository="true" id="e5534541-4a80-11e4-9553-005056977bd0" include="false" latest="latest">findjobj - find java handles of Matlab graphic objects</requiredAddOn>
</requiredaddons>
</param.required.addons>
<param.matlab.project.id />
@ -96,12 +96,49 @@
<fileset.depfun.excluded />
<fileset.package />
<build-deliverables>
<file location="${PROJECT_ROOT}" name="MCU Wrapper.mltbx" optional="false">E:\.WORK\MATLAB\mcu_matlab\MCU Wrapper.mltbx</file>
<file location="${PROJECT_ROOT}" name="MCU Wrapper.mltbx" optional="false">F:\Work\Projects\MATLAB\mcu_matlab_lib\MCU Wrapper.mltbx</file>
</build-deliverables>
<workflow />
<matlab>
<root>C:\Program Files\MyProgs\MATLAB\R2023a</root>
<toolboxes />
<root>C:\Program Files\MATLAB\R2023a</root>
<toolboxes>
<toolbox name="matlabcoder" />
<toolbox name="embeddedcoder" />
<toolbox name="gpucoder" />
<toolbox name="fixedpoint" />
<toolbox name="matlabhdlcoder" />
<toolbox name="neuralnetwork" />
</toolboxes>
<toolbox>
<matlabcoder>
<enabled>true</enabled>
</matlabcoder>
</toolbox>
<toolbox>
<embeddedcoder>
<enabled>true</enabled>
</embeddedcoder>
</toolbox>
<toolbox>
<gpucoder>
<enabled>true</enabled>
</gpucoder>
</toolbox>
<toolbox>
<fixedpoint>
<enabled>true</enabled>
</fixedpoint>
</toolbox>
<toolbox>
<matlabhdlcoder>
<enabled>true</enabled>
</matlabhdlcoder>
</toolbox>
<toolbox>
<neuralnetwork>
<enabled>true</enabled>
</neuralnetwork>
</toolbox>
</matlab>
<platform>
<unix>false</unix>