diff --git a/MCU Wrapper.mltbx b/MCU Wrapper.mltbx index e05745b..66056e2 100644 Binary files a/MCU Wrapper.mltbx and b/MCU Wrapper.mltbx differ diff --git a/McuLib/lib/McuLib.slx b/McuLib/lib/McuLib.slx index 422bd2b..4866f34 100644 Binary files a/McuLib/lib/McuLib.slx and b/McuLib/lib/McuLib.slx differ diff --git a/McuLib/m/appWrap.m b/McuLib/m/appWrap.m index e0f7fe7..cfb7a94 100644 --- a/McuLib/m/appWrap.m +++ b/McuLib/m/appWrap.m @@ -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 \ No newline at end of file diff --git a/McuLib/m/configJs.m b/McuLib/m/configJs.m new file mode 100644 index 0000000..f2cde98 --- /dev/null +++ b/McuLib/m/configJs.m @@ -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 diff --git a/McuLib/m/mainConfig.m b/McuLib/m/mainConfig.m new file mode 100644 index 0000000..a588212 --- /dev/null +++ b/McuLib/m/mainConfig.m @@ -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 \ No newline at end of file diff --git a/McuLib/m/mcuPath.m b/McuLib/m/mcuPath.m index f9d33cc..fefc8b8 100644 --- a/McuLib/m/mcuPath.m +++ b/McuLib/m/mcuPath.m @@ -8,7 +8,7 @@ classdef mcuPath end %% ADD PATH TO TABLE - + function addSourceFileTable(targetParamName, message) % Открываем проводник для выбора файлов [files, pathstr] = uigetfile({ ... diff --git a/McuLib/m/mexing.m b/McuLib/m/mexing.m index 5ab6e58..1b42f89 100644 --- a/McuLib/m/mexing.m +++ b/McuLib/m/mexing.m @@ -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 diff --git a/McuLib/m/periphConfig.m b/McuLib/m/periphConfig.m index b2520f7..ee6d26e 100644 --- a/McuLib/m/periphConfig.m +++ b/McuLib/m/periphConfig.m @@ -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__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; diff --git a/mcuwrapper.prj b/mcuwrapper.prj index 45493e9..18ce0d0 100644 --- a/mcuwrapper.prj +++ b/mcuwrapper.prj @@ -1,5 +1,5 @@ - + MCU Wrapper Razvalyaev wot890089@mail.ru @@ -27,7 +27,7 @@ false - findjobj - find java handles of Matlab graphic objects + findjobj - find java handles of Matlab graphic objects @@ -96,12 +96,49 @@ - E:\.WORK\MATLAB\mcu_matlab\MCU Wrapper.mltbx + F:\Work\Projects\MATLAB\mcu_matlab_lib\MCU Wrapper.mltbx - C:\Program Files\MyProgs\MATLAB\R2023a - + C:\Program Files\MATLAB\R2023a + + + + + + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + false