User Tools

Site Tools


javalikecalc

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
javalikecalc [2025/04/15 12:45] – created walhijavalikecalc [2026/09/11 13:57] (current) – Добавлен новый скрипт walhi
Line 4: Line 4:
  
 **Копирование** контроллеров/параметров дерева DAQ. Полезно для автоматизации процесса разворачивания проекта на N одинаковых контроллеров. **Копирование** контроллеров/параметров дерева DAQ. Полезно для автоматизации процесса разворачивания проекта на N одинаковых контроллеров.
-<code>+<code java>
 src = SYS.DAQ.ModBus.Test; src = SYS.DAQ.ModBus.Test;
 target = SYS.DAQ.ModBus; target = SYS.DAQ.ModBus;
Line 20: Line 20:
 </code> </code>
 ---- ----
 +
 +**Разворачивание** стандартного параметра на несколько логических. Развитие скрипта выше. Была у меня задача по модернизации опроса. Был параметр с опросом N штук сигнализаций (биты ''R/RI_b'' или ''C/CI''), по которому понадобилось выводить сообщения в журнал. Сделать это на стандартных параметрах, насколько мне известно, невозможно. Значит нужно преобразовать это в N логических параметров, которые будут обрабатывать данные. Ручками делать напряжно, так что создаем шаблон для копирования со всеми интересующими настройками и размножаем его на N с указанием источника данных. Размещаем создаваемые логические параметры прямо внутри источника (это законно и вполне работает). Но приходится указывать именно адреса регистров, а не ссылки относительные. Есть баг в OpenSCADA, когда при перезапуске программы относительные ссылки разрушаются. Пока что его не пофиксили.
 +
 +
 +<code java>
 +function setConfig(node, id, value){
 +    req = SYS.XMLNode("set").setAttr("path", node.nodePath() + id.replace("/", "%2f")).setText(value);
 +    SYS.cntrReq(req);
 +}
 +
 +
 +src = SYS.DAQ.ModBus.PLC1.prm_Alarm.prm_template;
 +target = SYS.DAQ.ModBus.PLC1.prm_Alarm;
 +
 +// Получаем список атрибутов, которые нужно перегнать в логические параметры
 +list = target.cfg("ATTR_LS").split("\n");
 +
 +out = "";
 +for (var i = 0; i < list.length; i++){
 +    c = list[i].split(":");
 +    // Пропуск комментариев и пустышек
 +    if (c[0].indexOf("#") == 0) continue;
 +    if (c.length < 4) continue;
 +    
 +    // "парсим" строку
 +    id = c[3];
 +    name = (c.length < 5)?c[3]:c[4]; // может быть без имени, только ID
 +    regType = c[0];
 +    regNum = c[1];
 +    regPerm = c[2];
 +    
 +    // Копирование элемента
 +    req = SYS.XMLNode("copy");
 +    req.setAttr("path", src.nodePath() + "/obj".replace("/", "%2f"));
 +    req.setAttr("src", src.nodePath());
 +    req.setAttr("dst", target.nodePath() + "prm_" + id);
 +    SYS.cntrReq(req);
 +    
 +    // Прописываем конфигурацию в созданный параметр
 +    newParam = target.nodeAt("prm_" + id);
 +    newParam.cfgSet("NAME", name);
 +    setConfig(newParam, "/cfg/prm/pr_3", regType + ":" + regNum + ":" + regPerm);
 +}
 +</code>
 +
 +
 +Что это за константа ''/cfg/prm/pr_3''? Я не совсем понимаю как они формируются, но это обозначение констант с вкладки конфигурации шаблона. Если навести на поле ввода, то внизу окна в подсказке будет строка с полным путем параметра и ''%2fcfg%2fprm%2fpr_3'' или ''%2fcfg%2fprm%2fel_7'' в конце. Как получить это из читаемого имени, указанного в шаблоне, я пока не знаю. 
 +
 +
 +----
 +
 +**Массовое изменение** логических параметров. Всплыл мой косяк, который надо исправить быстро и в нескольких местах. Свою ситуацию рассматривать не буду, но приведу аналогию. Есть у вас обработка входных данных, когда с контроллера читается целочисленное значение, которое надо умножить на 10 в степени x. Так часто поступают ради экономии памяти или ускорения опроса. И вот выходит обновление ПО контроллера и теперь надо умножать не на 0.01, а на 0.1. И это нужно поправить примерно в кажом из 500 шаблонов. Задачка простая, но неприятная. В итоге мне пришлось разбираться как это автоматизировать. 
 +
 +Для начала, в примере выше были магические константы ''%2fcfg%2fprm%2fel_7'', которые непонятно как формируются. На самом деле вполне очевидно, но не ясно почему разработчики сделали именно так. В интерфейсе редактирования шаблона обработки есть вкладка с кодом вычислений и переменными. Этот "магический" el_7 это порядковый номер в таблице. А теперь вопрос как преобразовать удобное имя в порядковый номер? В документации про это ни слова. Для шаблонов есть методы ''cfg'' и ''cfgSet'', но они работают только с именем или описанием. А остальные параметры нужно доставать через XML запросы. В примере выше есть функция ''setConfig'', которая записывает значение на вкладке конфигурации. Надо просто изменить запрос на ''get'' и указать правильный адрес.
 +
 +<code java>
 +io = SYS.XMLNode("get").setAttr("path", template.nodePath() + "/" + "/io/io".replace("/", "%2f"));
 +SYS.cntrReq(io);
 +</code>
 +
 +Далее уже работать с этим объектом. В нем будет 7 массивов по количеству столбцов таблицы с переменными: идентификатор, имя, тип, режим, атрибут, конфигурация, значение. Тут есть маленькая загвостка. Это XML объект, а не обычный объект OpenSCADA, так что работать с ним придется не совсем привычными методами. Свойства ''io.length'' нет, но есть его замена ''io.childSize()''. Для доступа к конкретному элементу массива недостаточно квадратных скобок. Нужно использовать метод ''io.childGet(i)'', а чтобы добраться до самого значения ещё и добавлять метод ''io.childGet(i).text()''. Неудобно, но сойдет. 
 +
 +Теперь про код обработки. Для начала надо описать задание: имя шаблона, имя переменной и новое значение. Дальше пройти по всему участку дерева опроса и все параметры, которые используют целевой шаблон, будут изменены.
 +
 +
 +<code java>
 +// Формируем задание
 +// Формат: массив объектов с полями:
 +// "template" - имя шаблона
 +// "param" - массив изменяемых констант (читаемые имена)
 +// "value" - массив новых значений
 +config = new Array();
 +
 +command = new Object();
 +command.template = "libName.Template1";
 +command.param = new Array();
 +command.value = new Array();
 +command.param.push("Mux");
 +command.value.push(0.01);
 +config.push(command);
 +
 +command = new Object();
 +command.template = "libName.Template2";
 +command.param = new Array();
 +command.value = new Array();
 +command.param.push("Mux");
 +command.value.push(0.01);
 +config.push(command);
 +
 +command = new Object();
 +command.template = "libName.Template3";
 +command.param = new Array();
 +command.value = new Array();
 +command.param.push("Mux");
 +command.value.push(0.01);
 +command.param.push("Timeout");
 +command.value.push(5);
 +config.push(command);
 +</code>
 +
 +После того, как сформировали задание, нужно получить из шаблонов интересующие поля. Преобразовываем для каждого шаблона имя переменной в код элемента. И в данном случае ещё нужно защититься от ошибок, чтобы менять только константы, а не связи.
 +
 +
 +<code java>
 +for (var i = 0; i < config.length; i++){
 +    // Получаем объект шаблона
 +    tmp = config[i].template.split(".");
 +    templateObject = SYS.DAQ.nodeAt("tmplb_" + tmp[0]).nodeAt("tmpl_" + tmp[1]);
 +    
 +    // Создаем в конфигурации задания дополнительное поле
 +    // с номером элемента в таблице, так как по имени обращаться нельзя
 +    config[i].el = new Array();
 +    
 +    const_mask = 0x40; // Взято из исходных кодов
 +    // TPrmTempl::CfgPublConst (0x40) - Публичная постоянная.
 +    // TPrmTempl::CfgLink (0x80) - Внешняя связь.
 +
 +    // Запрашиваем таблицу с вводами и выводами шаблона
 +    io = SYS.XMLNode("get").setAttr("path", templateObject.nodePath() + "/" + "/io/io".replace("/", "%2f"));
 +    SYS.cntrReq(io);
 +
 +    // Столбцы таблицы редактирования шаблона
 +    // Идентификатор, имя, тип, режим, атрибут, конфигурация, значение
 +    // Связь, переменная или константа описано в столбце "конфигурация"
 +    varNames = io.childGet(0);
 +    varSource = io.childGet(5);
 +    for (var j = 0; j < varNames.childSize(); j++){
 +        name = varNames.childGet(j).text();
 +        source = varSource.childGet(j).text().toInt();
 +        tmp = config[i].param.indexOf(name);
 +        if (tmp != -1){
 +            // Найден целевой параметр
 +            // Проверяем, что он имеет тип "константа"
 +            if (source & const_mask)
 +                config[i].el[tmp] = j;
 +            else
 +                config[i].el[tmp] = EVAL;
 +        }
 +    }
 +}
 +</code>
 +
 +Теперь можно пройтись по дереву. Указываем начальную точку и вызываем рекурсивную функцию, которая будет искать все логические параметры.
 +
 +
 +
 +<code java>
 +// Рекурсивная функция для прохода по дереву опроса
 +function runConfig(parent){
 +    attrs = parent.nodeList("prm_");
 +    for (var i = 0; i < attrs.length; i++){
 +        attr = parent.nodeAt(attrs[i]);
 +        tmpl = attr.cfg("TMPL");
 +        if (!tmpl.isEVal()){
 +            // Это логический параметр
 +            for (var j = 0; j < config.length; j++){
 +                if (config[j].template == tmpl){
 +                    // Есть задание для этого шаблона
 +                    for (var k = 0; k < config[j].param.length; k++){
 +                        if (!config[j].el[k].isEVal())
 +                            setConfig(attr, "/cfg/prm/el_" + config[j].el[k], config[j].value[k]);
 +                    }
 +                }
 +            }
 +        } else {
 +            // Это стандартный параметр. Вызываем рекурсивно, если есть дети
 +            if (attr.nodeList("prm_").length > 0) runConfig(attr);
 +        }
 +    }
 +}
 +
 +plc = SYS.DAQ.ModBus.PLC01;
 +runConfig(plc);
 +</code>
 +
 +Функция ''setConfig'' как в прошлом примере, но можно её и дополнить функционалом логирования, чтобы записать все измененные параметры.
 +
 +<code java>
 +function setConfig(node, id, value){
 +    req = SYS.XMLNode("get").setAttr("path", node.nodePath() + id.replace("/", "%2f")).setText(value);
 +    SYS.cntrReq(req);
 +    oldValue = req.text();
 +    if (oldValue != value) log += node.nodePath() + id.replace("/", "%2f") + " " + oldValue + " => " + value + "\n";
 +    req = SYS.XMLNode("set").setAttr("path", node.nodePath() + id.replace("/", "%2f")).setText(value);
 +    SYS.cntrReq(req);
 +}
 +</code>
  
javalikecalc.1744721159.txt.gz · Last modified: by walhi

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki