classdef periphConfig % Класс для управления конфигурацией периферии в маске Simulink % Динамически создает элементы маски на основе JSON конфигурации methods(Static) function updateMask(blockPath, config) % Основная функция обновления маски на основе конфигурации % Динамически создает вкладки и параметры для периферийных модулей % blockPath - путь к блоку Simulink % config - структура конфигурации из JSON файла mask = Simulink.Mask.get(blockPath); % Сохраняем состояние таблиц перед изменением маски tableNames = {'incTable', 'srcTable'}; columns_backup = customtable.save_all_tables(tableNames); try % Получаем настройки ширины строк из параметра маски rowWidth = str2double(get_param(blockPath, 'rowWidth')); % Очищаем контейнер конфигурации перед созданием новых элементов containerName = 'configTabAll'; periphConfig.clear_all_from_container(mask, containerName); % Получаем контейнер для вкладок конфигурации container = mask.getDialogControl(containerName); if isempty(container) error('Контейнер "%s" не найден в маске.', containerName); end % Обрабатываем конфигурацию если она не пустая if ~isempty(config) % Проходим по каждому модулю периферии (ADC, TIM, UART и т.д.) periphs = fieldnames(config); for i = 1:numel(periphs) periph = periphs{i}; % Сохраняем код модуля (исходники и инклюды) в скрытые параметры periphConfig.store_single_periph_code(mask, periph, config.(periph)); % Пропускаем модули без секции Defines if ~isfield(config.(periph), 'Defines') continue; end defines = config.(periph).Defines; defNames = fieldnames(defines); % Создаем вкладку для модуля периферии tabCtrl = container.addDialogControl('tab', periph); tabCtrl.Prompt = [periph ' Config']; % Отображаемое имя вкладки % Карта для отслеживания количества строк в каждой вкладке rowCountMap = containers.Map(); % Добавляем все параметры Defines в созданную вкладку for j = 1:numel(defNames) defPrompt = defNames{j}; def = defines.(defPrompt); % Добавление одного параметра конфигурации periphConfig.addConfig(mask, containerName, periph, defPrompt, def, rowCountMap, rowWidth); end end end % Создаем скрытые параметры для хранения кода периферии periphConfig.create_all_code_storage_params(blockPath, config); % Обновляем состояние маски (включение/выключение вкладок) periphConfig.update(); % Восстанавливаем таблицы после изменений customtable.restore_all_tables(tableNames, columns_backup); catch % В случае ошибки восстанавливаем таблицы customtable.restore_all_tables(tableNames, columns_backup); end end function update() % Обновление состояния маски - включение/выключение вкладок % на основе состояний чекбоксов управления blockPath = gcb; mask = Simulink.Mask.get(blockPath); % Читаем текущую конфигурацию config = configJs.read(blockPath); containerName = 'configTabAll'; container = mask.getDialogControl(containerName); % Получаем все параметры для проверки наличия чекбоксов paramsAll = mcuMask.collect_all_parameters(container); % Получаем имена всех вкладок в контейнере allTabs = container.DialogControls; allTabNames = arrayfun(@(t) t.Name, allTabs, 'UniformOutput', false); fieldsConfig = fieldnames(config); % Обрабатываем каждую вкладку for i = 1:length(allTabNames) periph = fieldsConfig{i}; % Проверяем наличие чекбокса управления для этой вкладки checkboxName = ['Tab_' periph '_Enable']; if ismember(checkboxName, paramsAll) % Чекбокс найден - управляем состоянием вкладки paramObj = mask.getParameter(checkboxName); val = paramObj.Value; try tab = container.getDialogControl(periph); if strcmpi(val, 'off') % Выключаем вкладку и очищаем связанные параметры tab.Enabled = 'off'; periphConfig.clear_tab_params(mask, config.(periph), periph); else % Включаем вкладку и синхронизируем параметры tab.Enabled = 'on'; periphConfig.sync_tab_params(mask, config.(periph), periph); end catch warning('Вкладка с именем "%s" не найдена.', periph); end else % Чекбокса нет - просто включаем вкладку try tab = container.getDialogControl(periph); tab.Enabled = 'on'; % Синхронизируем параметры если есть конфигурация if isfield(config, periph) periphConfig.sync_tab_params(mask, config.(periph), periph); end catch % Вкладка может быть необязательной - игнорируем ошибку end end end end function periphParamCallback(paramName) % Callback-функция для параметров периферии % Обрабатывает изменения чекбоксов и других параметров blockPath = gcb; mask = Simulink.Mask.get(blockPath); hObj = mask.getParameter(paramName); config = configJs.read(blockPath); container = mask.getDialogControl('configTabAll'); % === Обработка чекбоксов управления вкладками === exprTab = '^Tab_(\w+)_Enable$'; tokensTab = regexp(paramName, exprTab, 'tokens'); if ~isempty(tokensTab) periph = tokensTab{1}{1}; try tab = container.getDialogControl(periph); paramVal = hObj.Value; if strcmpi(paramVal, 'off') % Выключаем вкладку и очищаем параметры tab.Enabled = 'off'; if isfield(config, periph) periphConfig.clear_tab_params(mask, config.(periph), periph); end else % Включаем вкладку и синхронизируем параметры tab.Enabled = 'on'; if isfield(config, periph) periphConfig.sync_tab_params(mask, config.(periph), periph); end end catch warning('Ошибка обработки вкладки "%s".', periph); end return; end % === Обработка параметров, связанных с Sources/Includes === nameBase = paramName; paramNames = string({mask.Parameters.Name}); hasSources = any(paramNames == "Hidden_" + nameBase + "_Sources"); hasIncludes = any(paramNames == "Hidden_" + nameBase + "_Includes"); if hasSources || hasIncludes useVal = hObj.Value; % Получаем структуру конфигурации для этого параметра try valueStruct = configJs.get_field(config, nameBase); catch warning('Не удалось найти путь %s в config.', nameBase); return; end if strcmpi(useVal, 'on') % Сохраняем код если параметр включен try periphConfig.store_single_periph_code(mask, nameBase, valueStruct); catch warning('Не удалось сохранить параметры для %s.', nameBase); end else % Очищаем код если параметр выключен periphConfig.clear_single_periph_code_param(mask, nameBase); end return; end end function updatePeriphRunMexBat() % Обновление BAT-файла для компиляции с кодом периферии % Собирает все исходники и инклюды из скрытых параметров blockPath = gcb; % Восстанавливаем код периферии из скрытых параметров маски CodeStruct = periphConfig.restore_periph_code_from_mask(blockPath); periphPath = mcuPath.get('periphPath'); [periphPath, ~, ~] = fileparts(periphPath); % Добавляем код в BAT-файл компиляции periphConfig.addCodeBat(CodeStruct, periphPath); end end methods(Static, Access=private) function addHiddenParam(mask, containerName, nameBase, kind, existingParams) % Создание скрытого параметра для хранения кода % nameBase - базовое имя параметра % kind - тип ('Sources' или 'Includes') prettyName = strrep(nameBase, '_', ' '); paramName = ['Hidden_' char(nameBase) '_' kind]; % Проверяем не существует ли уже параметр if ismember(paramName, existingParams) return; end % Создаем скрытый параметр mask.addParameter( ... 'Name', paramName, ... 'Type', 'edit', ... 'Prompt', ['Hidden ' prettyName ' ' kind], ... 'Value', '', ... 'Visible', 'off', ... 'Container', containerName ... ); end function clear_tab_params(mask, configStruct, prefix, depth) % Рекурсивная очистка параметров вкладки % Используется при выключении вкладки if nargin < 4 depth = 0; end maxDepth = 3; % Ограничение глубины рекурсии fields = fieldnames(configStruct); for i = 1:numel(fields) key = fields{i}; value = configStruct.(key); paramName = [prefix '_' key]; if isstruct(value) if depth < maxDepth % Рекурсивный вызов для вложенных структур periphConfig.clear_tab_params(mask, value, paramName, depth + 1); end else % Очищаем параметры Sources/Includes if strcmp(key, 'Sources') || strcmp(key, 'Includes') baseName = configJs.get_final_name_from_prefix(prefix); periphConfig.clear_single_periph_code_param(mask, baseName); end end end end function sync_tab_params(mask, configStruct, prefix, depth) % Рекурсивная синхронизация параметров вкладки % Используется при включении вкладки if nargin < 4 depth = 0; end maxDepth = 3; fields = fieldnames(configStruct); for i = 1:numel(fields) key = fields{i}; value = configStruct.(key); paramName = [prefix '_' key]; if isstruct(value) if depth < maxDepth periphConfig.sync_tab_params(mask, value, paramName, depth + 1); end else if strcmp(key, 'Sources') || strcmp(key, 'Includes') baseName = configJs.get_final_name_from_prefix(prefix); periphConfig.store_single_periph_code(mask, baseName, configStruct); end end end end function create_all_code_storage_params(blockPath, config) % Создание всех скрытых параметров для хранения кода периферии mask = Simulink.Mask.get(blockPath); containerName = 'configTabAll'; container = mask.getDialogControl(containerName); existingParams = mcuMask.collect_all_parameters(container); % Создаем скрытую вкладку для хранения параметров кода tabName = 'hiddenCodeTab'; tab = mask.getDialogControl(tabName); if isempty(tab) tab = container.addDialogControl('tab', tabName); tab.Prompt = 'Hidden Code Settings'; tab.Visible = 'off'; else tab.Visible = 'off'; end % Рекурсивный обход конфигурации для создания параметров periphConfig.process_struct_recursive(mask, tabName, config, {}, existingParams); end function process_struct_recursive(mask, tabName, currentStruct, nameStack, existingParams) % Рекурсивный обход структуры конфигурации % Создает скрытые параметры для всех Sources и Includes fields = fieldnames(currentStruct); for i = 1:numel(fields) fieldName = fields{i}; value = currentStruct.(fieldName); newStack = [nameStack, fieldName]; if isstruct(value) % Проверяем наличие Sources и Includes в структуре hasSources = isfield(value, 'Sources'); hasIncludes = isfield(value, 'Includes'); if hasSources periphConfig.addHiddenParam(mask, tabName, fieldName, 'Sources', existingParams); end if hasIncludes periphConfig.addHiddenParam(mask, tabName, fieldName, 'Includes', existingParams); end % Рекурсивно продолжаем обход periphConfig.process_struct_recursive(mask, tabName, value, newStack, existingParams); end end end function cleanup_obsolete_code_params(blockPath, config) % Очистка устаревших скрытых параметров % Удаляет параметры для периферии, которой больше нет в конфигурации mask = Simulink.Mask.get(blockPath); maskParams = mask.Parameters; % Получаем список актуальных периферийных модулей validPeriphs = fieldnames(config); for i = 1:numel(maskParams) paramName = maskParams(i).Name; % Ищем параметры хранения кода expr = '^Hidden_(\w+)_(Sources|Includes)$'; tokens = regexp(paramName, expr, 'tokens'); if ~isempty(tokens) periph = tokens{1}{1}; % Удаляем параметр если периферия больше не существует if ~ismember(periph, validPeriphs) mask.removeParameter(paramName); end end end end function codeStruct = restore_periph_code_from_mask(blockPath) % Восстановление кода периферии из скрытых параметров маски % Собирает все Sources и Includes в одну структуру mask = Simulink.Mask.get(blockPath); maskParams = mask.Parameters; allSources = {}; allIncludes = {}; for i = 1:numel(maskParams) name = maskParams(i).Name; % Поиск параметров Sources tokensSrc = regexp(name, '^Hidden_(\w+)_Sources$', 'tokens'); if ~isempty(tokensSrc) val = maskParams(i).Value; if ischar(val) || isstring(val) valStr = strtrim(char(val)); % Пропускаем пустые значения if isempty(valStr) || strcmp(valStr, '[]') continue; end % Разбиваем на строки и фильтруем пустые lines = splitlines(valStr); lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines)); allSources = [allSources; lines]; end continue; end % Поиск параметров Includes tokensInc = regexp(name, '^Hidden_(\w+)_Includes$', 'tokens'); if ~isempty(tokensInc) val = maskParams(i).Value; if ischar(val) || isstring(val) valStr = strtrim(char(val)); if isempty(valStr) || strcmp(valStr, '[]') continue; end lines = splitlines(valStr); lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines)); allIncludes = [allIncludes; lines]; end continue; end end % Формируем результирующую структуру codeStruct = struct(); codeStruct.Sources = allSources; codeStruct.Includes = allIncludes; end function clear_all_from_container(mask, containerName) % Полная очистка контейнера - удаление всех параметров и вкладок container = mask.getDialogControl(containerName); if isempty(container) warning('Контейнер "%s" не найден.', containerName); return; end % Собираем все параметры в контейнере paramsToDelete = mcuMask.collect_all_parameters(container); % Удаляем все параметры for i = 1:numel(paramsToDelete) try mask.removeParameter(paramsToDelete{i}); catch warning('Не удалось удалить параметр %s', paramsToDelete{i}); end end % Рекурсивно удаляем все вкладки mcuMask.delete_all_tabs(mask, container); end function res = addCodeBat(codeConfig, codePath) % Добавление кода периферии в BAT-файл компиляции try % Формируем строки для BAT-файла srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath); incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath); % Обновляем BAT-файл res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); catch res = 1; % Ошибка end end function res = addUserFunctions(userCodeConfig) % Добавление пользовательских функций в код обёртки initFuncsText = ''; simFuncsText = ''; deinitFuncsText = ''; if isfield(userCodeConfig, 'Functions') funcs = userCodeConfig.Functions; % Обрабатываем функции инициализации if isfield(funcs, 'PeriphInit') initFuncs = funcs.PeriphInit; initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n'); end % Обрабатываем функции симуляции if isfield(funcs, 'PeriphSimulation') simFuncs = funcs.PeriphSimulation; simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n'); end % Обрабатываем функции деинициализации if isfield(funcs, 'PeriphDeinit') deinitFuncs = funcs.PeriphDeinit; deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n'); end % Записываем функции в файл обёртки res = periphConfig.writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText); end end function res = writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText) % Запись пользовательских функций в файл mcu_wrapper.c wrapPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c'); res = 1; try % Читаем текущее содержимое файла code = fileread(wrapPath); code = regexprep(code, '\r\n?', '\n'); % Вставляем функции в соответствующие секции code = editCode.insertSection(code, '// PERIPH INIT', initFuncsText); code = editCode.insertSection(code, '// PERIPH SIM', simFuncsText); code = editCode.insertSection(code, '// PERIPH DEINIT', deinitFuncsText); % Записываем обновленный файл fid = fopen(wrapPath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи'); end fwrite(fid, code); fclose(fid); res = 1; catch ME error('Ошибка: неудачная запись в файл при записи файла: %s', ME.message); end end function addConfig(mask, containerName, periphName, defPrompt, def, rowCountMap, rowWidth) % Добавление одного параметра конфигурации в маску % mask - объект маски % containerName - имя контейнера % periphName - имя периферийного модуля % defPrompt - имя параметра % def - структура с описанием параметра % rowCountMap - карта для отслеживания строк % rowWidth - ширина строки % Инициализация счетчика строк для этого модуля if ~isKey(rowCountMap, periphName) rowCountMap(periphName) = 0; end rowCount = rowCountMap(periphName) + 1; % Определяем начинать ли новую строку if ~isfield(def, 'NewRow') def.NewRow = mod(rowCount - 1, rowWidth) == 0; elseif def.NewRow == true rowCount = 1; end rowCountMap(periphName) = rowCount; % Получаем контейнер container = mask.getDialogControl(containerName); if isempty(container) error('Контейнер "%s" не найден в маске.', containerName); end % Создаем вкладку если её нет tabCtrl = mask.getDialogControl(periphName); if isempty(tabCtrl) tabCtrl = container.addDialogControl('tab', periphName); tabCtrl.Prompt = [periphName ' Config']; end % Определяем тип параметра switch lower(def.Type) case 'checkbox' paramType = 'checkbox'; case 'edit' paramType = 'edit'; case 'popup' paramType = 'popup'; otherwise return; % Пропускаем неизвестные типы end % Создаем валидное имя параметра paramName = matlab.lang.makeValidName(defPrompt); % Устанавливаем значение по умолчанию if strcmp(paramType, 'popup') 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; end else val = def.Default; if islogical(val) valStr = periphConfig.ternary(val, 'on', 'off'); elseif isnumeric(val) valStr = num2str(val); elseif ischar(val) valStr = val; else error('Unsupported default value type for %s.%s', periphName, defPrompt); end end % Добавляем параметр в маску if strcmp(paramType, 'popup') param = mask.addParameter( ... 'Type', paramType, ... 'Prompt', def.Prompt, ... 'Name', paramName, ... 'Container', periphName ... ); else param = mask.addParameter( ... 'Type', paramType, ... 'Prompt', def.Prompt, ... 'Name', paramName, ... 'Value', valStr, ... 'Container', periphName ... ); end param.Evaluate = 'off'; if def.NewRow param.DialogControl.Row = 'new'; else param.DialogControl.Row = 'current'; end if isfield(def, 'Def') if strcmp(paramType, 'popup') 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 end callback = sprintf('try periphConfig.periphParamCallback("%s"); catch end', paramName); param.Callback = callback; end %% ELEMENTARY FUNCTIONS - базовые вспомогательные функции function clear_single_periph_code_param(mask, periph) % Очистка кода одного модуля периферии paramNames = { ['Hidden_' char(periph) '_Sources'], ['Hidden_' char(periph) '_Includes'] }; for i = 1:numel(paramNames) paramName = paramNames{i}; try param = mask.getParameter(paramName); param.Value = ''; catch % Параметр не существует - игнорируем end end end function store_single_periph_code(mask, periph, code) % Сохранение кода одного модуля периферии в скрытые параметры if isfield(code, 'Sources') paramName = ['Hidden_' char(periph) '_Sources']; try param = mask.getParameter(paramName); param.Value = configJs.convert_code_value(code.Sources); catch mcuMask.disp(0, ['Параметр ' paramName ' не найден']); end end if isfield(code, 'Includes') paramName = ['Hidden_' char(periph) '_Includes']; try param = mask.getParameter(paramName); param.Value = configJs.convert_code_value(code.Includes); catch mcuMask.disp(0, ['Параметр ' paramName ' не найден']); end end end function res = ternary(cond, valTrue, valFalse) % Вспомогательная функция - тернарный оператор if cond res = valTrue; else res = valFalse; end end end end