User Tools

Site Tools


javalikecalc

На данной странице собраны решения некоторых задач на встроенном в OpenSCADA языке JavaLikeCalc.


Копирование контроллеров/параметров дерева DAQ. Полезно для автоматизации процесса разворачивания проекта на N одинаковых контроллеров.

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);

Разворачивание стандартного параметра на несколько логических. Развитие скрипта выше. Была у меня задача по модернизации опроса. Был параметр с опросом N штук сигнализаций (биты R/RI_b или C/CI), по которому понадобилось выводить сообщения в журнал. Сделать это на стандартных параметрах, насколько мне известно, невозможно. Значит нужно преобразовать это в N логических параметров, которые будут обрабатывать данные. Ручками делать напряжно, так что создаем шаблон для копирования со всеми интересующими настройками и размножаем его на N с указанием источника данных. Размещаем создаваемые логические параметры прямо внутри источника (это законно и вполне работает). Но приходится указывать именно адреса регистров, а не ссылки относительные. Есть баг в OpenSCADA, когда при перезапуске программы относительные ссылки разрушаются. Пока что его не пофиксили.

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);
}

Что это за константа /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 и указать правильный адрес.

io = SYS.XMLNode("get").setAttr("path", template.nodePath() + "/" + "/io/io".replace("/", "%2f"));
SYS.cntrReq(io);

Далее уже работать с этим объектом. В нем будет 7 массивов по количеству столбцов таблицы с переменными: идентификатор, имя, тип, режим, атрибут, конфигурация, значение. Тут есть маленькая загвостка. Это XML объект, а не обычный объект OpenSCADA, так что работать с ним придется не совсем привычными методами. Свойства io.length нет, но есть его замена io.childSize(). Для доступа к конкретному элементу массива недостаточно квадратных скобок. Нужно использовать метод io.childGet(i), а чтобы добраться до самого значения ещё и добавлять метод io.childGet(i).text(). Неудобно, но сойдет.

Теперь про код обработки. Для начала надо описать задание: имя шаблона, имя переменной и новое значение. Дальше пройти по всему участку дерева опроса и все параметры, которые используют целевой шаблон, будут изменены.

// Формируем задание
// Формат: массив объектов с полями:
// "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);

После того, как сформировали задание, нужно получить из шаблонов интересующие поля. Преобразовываем для каждого шаблона имя переменной в код элемента. И в данном случае ещё нужно защититься от ошибок, чтобы менять только константы, а не связи.

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;
        }
    }
}

Теперь можно пройтись по дереву. Указываем начальную точку и вызываем рекурсивную функцию, которая будет искать все логические параметры.

// Рекурсивная функция для прохода по дереву опроса
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);

Функция setConfig как в прошлом примере, но можно её и дополнить функционалом логирования, чтобы записать все измененные параметры.

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);
}
javalikecalc.txt · Last modified: by walhi

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki