User Tools

Site Tools


javalikecalc

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
javalikecalc [2026/08/28 11:36] – Добавлен ещё один скрипт walhijavalikecalc [2026/09/11 13:57] (current) – Добавлен новый скрипт walhi
Line 1: Line 1:
 +На данной странице собраны решения некоторых задач на встроенном в OpenSCADA языке JavaLikeCalc.
 +
 +----
 +
 +**Копирование** контроллеров/параметров дерева DAQ. Полезно для автоматизации процесса разворачивания проекта на N одинаковых контроллеров.
 +<code java>
 +src = SYS.DAQ.ModBus.Test;
 +target = SYS.DAQ.ModBus;
 +targetName = "NewPLC";
 +req = SYS.XMLNode("copy");
 +req.setAttr("path", src.nodePath() + "/obj".replace("/", "%2f"));
 +req.setAttr("src", src.nodePath());
 +if (typeof(src) == "TCntrNode:TController"){
 + pref = "cntr_";
 +} else {
 + pref = "prm_";
 +}
 +req.setAttr("dst", target.nodePath() + pref + targetName);
 +SYS.cntrReq(req);
 +</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.1787917015.txt.gz · Last modified: by walhi

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki