<< Click to Display Table of Contents >> Navigation: Modbus Universal MasterOPC Server > Руководство по языку Lua 5.1 > Примеры и полезности > Работа с ODBC > Запись в базу OPC HDA архива |
•Настройка таблицы для PostgreSQL
Рассматривается пример из конфигурации ODBC Example.mpp. Конфигурация состоит из имитационных тегов с включенным режимом HDA, а также отдельным узлом для работы с ODBC. На основе данного примера вы можете разработать собственный скрипт адаптированный под вашу конфигурацию и базу данных.
Мы будем опрашивать все три тега, и записывать их в таблицу MS SQL следующей структуры:
Примечание. Обратите внимание что длина имени Name - 50 символов, если в вашем источнике более длинные имена, то задайте длину имени больше. Также следует иметь ввиду, что данная структура таблицы подходит только для хранения чисел и логических значений - со строковым типом и временем скрипт работать не будет.
При этом ID объявлен как основной ключ, с включенным инкрементом.
Имя таблицы - ArchiveValues.
Примечание. По ссылке находится готовая настроенная база данных - можете также использовать ее. Для этого в Management Studio выполните Databases - Restore Database.
Полный код примера:
local host = "MSSQLConnect"
local login =""
local password = ""
local env;
local CONN=nil;
local PathToDevice="Simulator.script"; -- path to device
local TagTable={}; --table of name tags
local TimeTable={}; --table of time last time HDA-tags values
function WriteToBase(NameTag,LastTime)
if CONN==nil then
return true,nil;
end
Num,Archive,LastTime=server.ReadHDAFromTag(NameTag,LastTime,false); --read archive value
if Num==0 then
return true,nil;
end;
Values=Archive[1];
Times=Archive[3];
local SQLQuery="INSERT INTO ArchiveValues VALUES "; --header of query
for i=1,table.maxn(Values),1 do
SQLQuery=SQLQuery..string.format("('%s','%s','%s')",NameTag,Values[i],Times[i]); --concating and formating string query
if i~=table.maxn(Values) then
SQLQuery=SQLQuery..", ";
end;
end;
--server.Message("SQLQuery=",SQLQuery); --summary SQL query
res,s = odbc.conn_execute(CONN,SQLQuery); --write values in the table
if res==nil then --error in request
odbc.conn_close(CONN); --close connection
odbc.env_close(env) --close ODBC object
CONN=nil;
server.Message("Error=",s);
return true,nil;
end;
return false,LastTime;
end;
-- initialize
function OnInit()
local CountTag=server.GetCountTags(PathToDevice);
for i=0,CountTag-1,1 do
local err,Tab=server.GetAttributeTagByNumber(i,PathToDevice); --get attribute tag from target device
if err==false and Tab[11]==true then --not error and this is tag is HDA
local Name=Tab[2]; --full name of tag
table.insert(TagTable,Name); --adding to table tags
table.insert(TimeTable,nil); --write nil value to table of times
end
end
end
-- deinitialize
function OnClose()
end
-- handling
function OnBeforeReading()
if CONN==nil then
env = odbc.env_create(); --create ODBC object
server.Message("Connection attempt");
CONN,s = odbc.env_connect(env,host,login,password); --connecting
if CONN==nil then
server.Message("Error=",s); -- no connected
return;
end;
end;
for i=1,table.maxn(TagTable),1 do
local err,LastTime=WriteToBase(TagTable[i],TimeTable[i]) --run function
if err==false then
TimeTable[i]=LastTime; --save last time value
end;
end
В заголовке скрипта задаются параметры подключения к базе данных - имя ODBC коннектора, логин и пароль. При необходимости данные параметры можно вынести как дополнительные свойства устройства. Также в заголовке задается путь к устройству с тегами, которые будем экспортировать в базу.
В секции OnInit производится получение списка HDA тегов целевого устройства. Для этого можно воспользоваться функцией server.GetAttributeTagByNumber, в которую вторым параметром можно передать путь к нужному узлу или устройству. Получить количество тегов можно с помощью функции server.GetCountTags, в которую также как аргумент передается путь к нужному нам устройству. В цикле перебираем теги устройства, проверяем что тег получен успешно (нет флага ошибки), а также то, что тег является HDA тегом (11 элемент полученной таблицы аргументов). Полный путь тега добавляем в таблицу тегов, а в таблицу времени записываем значение nil - используя значения этой таблицы, мы будем указывать с какого момента делать выборку из HDA архива.
function OnInit()
local CountTag=server.GetCountTags(PathToDevice);
for i=0,CountTag-1,1 do
local err,Tab=server.GetAttributeTagByNumber(i,PathToDevice); --get attribute tag from target device
if err==false and Tab[11]==true then --not error and this is tag is HDA
local Name=Tab[2]; --full name of tag
table.insert(TagTable,Name); --adding to table tags
table.insert(TimeTable,nil); --write nil value to table of times
end
end
end
В секции OnBeforeReading проверяется наличие ODBC объекта, и если его значение равно nil, то происходит создание ODBC объекта с помощью функции odbc.env_create. Затем происходит попытка установления соединения (если оно не было установлено ранее) с помощью функции odbc.env_connect. В случае ошибки происходит выход из скрипта. Затем в цикле происходит перебор всех значений таблицы TagTable, и для каждого тега - с его именем и меткой времени последнего считанного значения, происходит вызов пользовательской функции WriteToBase.
В случае успешности вызова функции метка времени последнего значения сохраняется в таблицу со временем.
function OnBeforeReading()
if CONN==nil then
server.Message("Connection attempt");
CONN,s = odbc.env_connect(env,host,login,password); --connecting
if CONN==nil then
server.Message("Error=",s); -- no connected
return;
end;
end;
for i=1,table.maxn(TagTable),1 do
local err,LastTime=WriteToBase(TagTable[i],TimeTable[i]) --run function
if err==false then
TimeTable[i]=LastTime; --save last time value
end;
end
Основные действия с базой производятся в теле функции WriteToBase. В качестве аргумента в функцию поступает имя тега и его последняя метка времени. Переменная соединения с ODBC - CONN, является глобальной, поэтому доступна из всех функций скрипта.
С помощью функции server.ReadHDAFromTag производится считывание архива тега. Итогом формируются переменные - количество записей, таблица архива, и метка времени последнего значения. После проверки что у тега есть свежие значения (проверка переменной Num), происходит создание двух таблицы - Values, содержащая таблицу значения и Times - содержащая метки времени (таблица с признаками качества в данном примере не применяется). Затем происходит формирование SQL запроса. Чтобы вставить несколько значений в таблицу за один запрос, запрос должен иметь следующий синтаксис:
INSERT INTO ArchiveValues VALUES (Значение1,Значение2),(Значение2,Значение3),....(ЗначениеN,ЗначениеN)
Сначала происходит инициализация переменной SQLQuery заголовком запроса. Затем начинается перебор всех значений таблицы. С помощью функции string.format происходит соединение трех элементов в строку - имени тега, значения и времени, разделенные запятыми и обрамленные скобками. Если элемент таблицы не является последним то добавляется запятая. Посмотреть форму итогового SQL запроса можно раскомментировав строчку --server.Message("SQLQuery=",SQLQuery);
После того как запрос сформирован происходит его вызов с помощью функции odbc.conn_execute. Если при выполнении запроса произойдет ошибка, то соединение закрывается, и возвращается код ошибки. В противном случае возвращается, что функция выполнена успешно и возвращается метка времени последнего значения.
function WriteToBase(NameTag,LastTime)
Num,Archive,LastTime=server.ReadHDAFromTag(NameTag,LastTime,false); --read archive value
if Num==0 then
return true,nil;
end;
Values=Archive[1];
Times=Archive[3];
local SQLQuery="INSERT INTO ArchiveValues VALUES "; --header of query
for i=1,table.maxn(Values),1 do
SQLQuery=SQLQuery..string.format("('%s','%s','%s')",NameTag,Values[i],Times[i]); --concating and formating string query
if i~=table.maxn(Values) then
SQLQuery=SQLQuery..", ";
end;
end;
--server.Message("SQLQuery=",SQLQuery); --summary SQL query
res,s = odbc.conn_execute(CONN,SQLQuery); --write values in the table
if res==nil then --error in request
odbc.conn_close(CONN); --close connection
CONN=nil;
server.Message("Error=",s);
return true,nil;
end;
return false,LastTime;
end;
Результат работы конфигурации в режиме исполнения:
В случае с PostgreSQL настройка столбцов таблицы будет выглядеть следующим образом:
Обратите внимание на типы данных, а также на то что столбец ID идет последним. Если разместить его первым, то SQL запрос из примера скрипта не будет выполняться.