На данной странице собраны решения некоторых задач на встроенном в 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);
}