Mr.Doors


Rambler's Top100



Вольт Маркет
Опыт работы в SCADA iFix с использованием ODBC
ImageВ статье подробно  излагается опыт организации работы в SCADA системе iFix версии 3.5 на конкретном примере АСУТП крупного предприятия( электроэнергетика). Статья будет интересна  и полезна всем тем, кто занимается наладкой и обслуживанием SCADA   систем, поддерживающих работу с реляционными базами данных.

До внедрения iFix на предприятии использовалась ТМ98 –  программа отображения информации от комплекса телемеханики «СИРИУС»(творчества тогда еще Новополоцкого унитарного предприятия Томского НИИ Автоматики и электромеханики), позднее выросшая до  ТМ2000 (ООО «Новософт). Но системы телемеханики и автоматики предприятия активно пополнялись новым парком разношерстного оборудования. Возник вопрос внедрения  новой SCADA системы, с более широкими и универсальными возможностями интеграции на нижнем уровне.  Выбор руководства пал на SCADA систему iFix версии 3.5 – на тот момент; самая последняя из русифицированных. Внедрение и наладку iFix на предприятии осуществила одна из фирм, специалисты которой прошли обучение в специализированном центре. Но конечному пользователю, – диспетчерам АРМ,- нововведение категорически не понравилось. По их мнению, большего для оперативной работы, чем было в старушке ТМ98, им и подавно не надо. С доводами диспетчеров нельзя было не согласиться , - наладчики в этом смысле явно схалтурили, на скорую руку подогнав готовый проект под требуемую конфигурацию.  Тем не менее, руководство работу приняло, и отдел телемеханики предприятия, и я лично, занялся активным изучением нововведения, с целью подгонки продукта конечному пользователю - диспетчеру. На момент написания статьи, в базе данных iFix предприятия обрабатывалось более 1,5 тысячи аналоговых входов(AI), около  4-х тысяч дискретных входов (DI) и около тысячи дискретных выходов(DO).

Итак наши диспетчера желали одного –работать с системой , интерфейс которой подобен ТМ98. Поэтому мною самим себе была поставлена задача, чтобы оператор( диспетчер) не держа перед собой инструкцию по эксплуатации, смог интуитивно работать с новой SCADA системой.

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

По моему получилось довольно неплохо. Вот что было:

Image

Image

И вот что стало:

Image

Image

Второй основной задачей стал вопрос создания архива тревог,  и быстрого и удобного средства для работы с этим архивом. В системе, где основные объекты контроля и связанные сними события представлены в виде дискретных сигналов – это действительно важно. Сразу следует отметить, что в нашу лицензию не входили опции iHistorian, поэтому для решения этой проблемы  понадобилось найти другие пути.

Каждому, кто работал с iFix, известно, что решить задачу ретроспективы, просмотра архива тревог и отчетов по тревогам, стандартными средствами iFix, можно двумя путями :

  1. iFix может сбрасывать тревоги в файлы. На каждый день создается отдельный файл текстового содержания (*.alm). Средний размер суточного файла тревог в системе равнялся приблизительно 1 МВ. При попытке написании программы работающей с файлами тревог, выяснилось, -  что даже простое считывание строки с необходимыми параметрами, из одного такого файла,  занимает около десяти секунд. А если учесть, что необходимо будет обработать данные за месяц, да ещё при выводе списком отсортировать по дате/времени, то становиться понятно, что данный метод нам не подходит.
  2. iFix может сбрасывать тревоги в реляционную  базу данных – работа с БД осуществляется по стандарту ODBC и требует определенных навыков. Попробовав этот метод, выяснилось, что  выборка по запросу из БД размером около 250 Мбайт занимает не более секунды. Поэтому было принято решение двигаться по этому направлению.

На первый взгляд, всё казалось не так сложно. Для  начала необходимо зарегистрировать базу данных с которой будет работать iFix в качестве источника, доступ к которому будет осуществляться через драйвер ODBC. Для этого выполняем следующие действия:

  1. Создаем в Microsoft Access  пустую базу, например  RetroAlm.mdb.  Тип базы может быть любой : Oracle, Sybase, SQL Server либо Access. Мой выбор пал на Access просто потому, что на машине уже был установлен пакет Microsoft Office2003 вместе с Access . На первоначальной стадии наладки базы данных тревог, необходима установка одной их версий Access. 
  2. В панели управления Windows->Администрирование->источник данных ODBC создаём пользовательский DSN работающий через драйвер Microsoft Access

Image

Созданный источник данных будет работать только для создавшего его пользователя. Чтобы источник данных был доступен для всех пользователей, используется закладка «системный DSN», при этом необходимо иметь права администратора:

Image

Теперь необходимо заставить iFix скидывать тревоги  в нужную базу. Это решается следующим образом: в SCU включаем службу тревог ODBC: SCU->конфигурация -> тревоги->служба тревог ODBS-> включить.

Здесь же задаём конфигурацию службы тревог ODBC с заданием источника данных

Image

При первом запуске службы, в настройке конфигурации iFix ODBC ставим галочку напротив «Если таблица не найдена, создать её при выполнении». В дальнейшем эту галочку можно убрать, чтобы случайно не затереть данные в уже имеющейся таблице. Теперь после перезагрузки iFix мы увидим следующие сообщение в Alarm History:

18.09.2008 09:55:44,9 [NODENAME] AlmODBC - Инициализация загрузчика. Подождите, пожалуйста ...                                     
18.09.2008 09:55:45,4 [NODENAME] AlmODBC - Соединение с базой данных ...                                                           
18.09.2008 09:55:46,6 [NODENAME] AlmODBC - Связался с базой данных                                                                 
18.09.2008 09:55:47,2 [NODENAME] AlmODBC - Загрузка тревог в базу данных       


В Mission Control мы увидим следующее:

Image

Теперь все тревоги пишутся в таблицу FIXALARMS базы данных с идентификатором Date Source.

Казалось бы, теперь осуществляй выборку данных из таблицы FIXALARMS  с помощь элементов VisiconX, и получай какие захочешь данные по тревогам в желаемой форме. Не переходя к описанию с элементами VisiconX в WorkSpace, сразу скажу, что уже через месяц начала работы с этим методом,  выяснился основной его недостаток. После того, как в таблице FIXALARMS насчитывается более 10 тысяч записей – время простейшего запроса начинает измерятся десятками секунд. База данных растет в размере, пожирая свободное пространство на диске, что в конце концов приводит к критической ошибке всей системы. Становиться понятно – необходимо разработать механизм сортировки записей в базе данных и удаления избыточной информации.

Таким образом, для оптимизации работы, необходимо решить поэтапно следующие задачи:

  1. Ограничить размер таблицы FIXALARMS, периодически (например раз в сутки)  распределяя записи в другие таблицы и удаляя отсортированные и ненужные записи
  2. Ограничить число записей в создаваемых таблицах, удаляя наиболее старые из них

Открываем созданную iFix базу данных тревог в Access, предварительно закрыв iFix. Для каждого тэга с включенной тревогой создаём в базе данных тревог  таблицу, структура и поля которой аналогичны таблице FIXALARMS. Имя созданной таблицы соответствует имени тэга в базе данных. В Access это можно сделать простым копированием/вставкой. Конструктор таблицы FIXALARMS в моей базе данных тревог имеет вид:

Image

Также создадим таблицу, в которой будет храниться список сигналов, для которых необходимо вести ретроспективу. Конструктор моей таблицы  в Access имеет вид:

Image 

Первое поле TS_TAGNAME хранит список тегов дискретного ввода (DI), для которых ведется ретроспектива. Второе поле TU_TAGNAME хранит список тегов дискретного вывода (DO). Поле TU_TAGNAME необходимо исходя из специфики контролируемых объектов в системе, а именно – коммутационных аппаратов, для которых характерен тег состояния DI и тег управления DO. Для дальнейшего упрощения составления ретроспективы с подобными объектами и необходимо это поле. Для других объектов в системе поле TU_TAGNAME просто не заполняется.
Записи в таблицу TsTu_Table вносятся вручную. Но это процесс не составляет много времени: в базе данных iFix делаем выборку элементов DI с включенной тревогой, и копируем значение полей ALM_TAGNAME в таблицу TsTu_Table. Пример заполненной таблицы  TsTu_Table в базе данных тревог:

Image 

Теперь для каждой записи таблицы TsTu_Table создадим отдельную таблицу, в которой мы будем хранить записи тревог по объекту с именем TS_TAGNAME. Структура таких отдельных таблиц ,будет в точности соответствовать структуре таблицы FIXALARMS, а имена таблиц соответствовать значению записей поля TS_TAGNAME таблицы TsTu_Table.

Поскольку процесс создания таких таблиц вручную – процесс достаточно долгий при большом количестве записей в таблице TsTu_Table, - тут же, в Access, напишем макрос, который всё сделает за нас. Заодно исключим и все возможные ошибки, связанные с человеческим фактором.

Итак,  в открытой в Access базе данных тревог создаем новый модуль с именем  Module1. В открывшемся окне Microsoft Visual Basic сразу открываем закладку Tools→References…  . Ищем в открывшемся списке библиотеку Microsoft DAO 3.6 Library и ставим напротив её галочку, включая файл библиотеки в наш модуль. Исходный текст модуля:

Image 

Подгоняем  значения параметров методов ConnectionString и OpenDatebase под необходимые значения, компилируем и запускаем: Run→Run Sub/User Form→ MacroName:mac1→Run. Ждем пока не появиться окошко с сообщение «creating», что свидетельствует о успешном выполнения макроса mac1. Закрываем Visual Basic и наблюдаем,  в списке таблиц нашей базы данных тревог, созданные таблицы.

На этой стадии работа с Access закончена. Теперь работать с базой данных тревог будем только через iFix.

Как уже отмечалось выше, теперь надо позаботиться о том, чтобы не допустить переполнения таблиц избыточными данными. Для этого создадим в iFix расписание с двумя событиями по времени (таймерами). Первое событие раз в сутки будет осуществлять выборку тревог из таблицы FIXALARMS по списку таблицы TsTu_Table, и перемещать эти тревоги в соответствующие таблицы. После произведенной сортировки, считаем, что вся нужная нам информация из таблицы FIXALARMS собрана, поэтому удаляем из таблицы все записи с датой старше текущих суток.

Второй таймер будет раз в сутки пересматривать записи в таблицах базы данных тревог, и если их количество в таблице превышает определенное количество – удаляет более старые.

В системном дереве WorkSpace в папке «Расписания» создаем Schedule «RetroWork». Добавляем две новые записи по времени: «SortTablTimer» - таймер ежедневной сортировки и очистки таблицы FIXALARM, и «DelAltRec» - таймер ежедневной очистки таблиц базы данных тревог:

Image 

Для обоих таймеров тип запуска – ежедневно, с разницей по времени начала – 10..20 минут( сортировка, в зависимости от загрузки процессора и количества тревог, занимает определенное время и ресурс ЦП).
Выполнение сортировочных процедур предполагаем выполнять в фоновом режиме. Поэтому в свойствах планировщика для «RetroWork» выбираем состояние при выполнении – выполнять в фоновом режиме. Соглашаемся с предложением iFix добавить созданное расписание в предпочтения пользователя, а вот от предложения запустить фоновую задачу прямо сейчас – отказываемся, -  задача пока не сконфигурирована. Переходим, собственно,  к написанию скрипта, для созданных таймеров. Ниже приведен листинг расписания «Retrowork» с  подробным описанием( _  – символ переноса строки):


Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal_ lpClassName As String, ByVal lpWindowName As String) As Long

Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow_ As Long) As Long

Const SW_HIDE = 0
`деларируем две функции библиотеки user32 и константу SW_HIDE – далее используется `для того, чтобы после запуска FixBackgroundServer, отвечающий за выполнение фоновой `задачи, - не висел на панели задач 

Dim ts_tag, tu_tag, CdDate, ProejStr As String
Dim CurDate As Date
Dim i, j As Byte
`объявляем глобальные переменные

Private cn As ADODB.Connection
`объявляем объект класса Connection библиотеки ADODB хранящий ссылку на базу данных
`до начала объявления не забываем добавить библиотеку ADODB в наш проект Retro_Work
` Tools→ References…→ Microsoft ActiveX Data Objects 2.7 Lybraly

Private rs, ds As ADODB.Recordset
`два объекта хранящие результаты запроса

Private Declare Function PauseAlarmODBC Lib "missionvba" () As Long
`функция будет приостанавливать работу AlarmODBC службы, чтобы освободить доступ к `базе данных тревог


Private Declare Function ContinueAlarmODBC Lib "missionvba" () As Long
`функция продолжает нормальную работу AlarmODBC службы

Private Sub CFixScheduler_Initialize()
Dim hwnd As Long
hwnd = FindWindow(vbNullString, "iFIX Background Server")
ShowWindow hwnd, SW_HIDE
End Sub
`после такой инициализации FixBackgroundServer будет работать как служба


 Private Sub DelAltRec_OnTimeOut(ByVal lTimerId As Long)
`таймер ежедневной очистки таблиц базы данных тревог
   
    PauseAlarmODBC
    ProejStr = System.ProjectPath
    Set cn = New ADODB.Connection
    `создаём ADO Connection объект

    With cn
          .ConnectionString = "Provider=microsoft.jet.OLEDB.4.0;Data Source=" + _             ProejStr + "\Alm\MDB\RetroAlm.mdb"
          .Open
    End With
    `соединяемся с БД и открываем её
   
    On Error GoTo errHandl2

    Set rs = New ADODB.Recordset
    `создаём ADO Recordset объект
   
    rs.Open "select * from  TsTu_Table", cn, , adLockReadOnly
    `выбираем все записи из таблицы TsTu_Table
   
    Set ds = New ADODB.Recordset
    `создаём ещё один ADO Recordset объект
    ds.CursorLocation = adUseClient
   
    Do While Not rs.EOF
        ts_tag = rs("TS_TAGNAME")
        ds.Open ts_tag, cn, , adLockReadOnly
      `открываем таблицу с именем записи из поля TS_TAGNAME таблицы TsTu_Table
        If ds.EOF = False Then
            ds.MoveLast
            d = ds.RecordCount
            If d > 250 Then
                   ts_tag = ts_tag + " "
sql = "delete from " + ts_tag + "where_ (((ALM_NATIVETIMELAST)<Date()-31));"
                 cn.Execute sql
            `если число записей в отрываемой таблице больше 250 – удаляем
`которые по дате старше месяца
            End If
        End If
        ds.Close
        rs.MoveNext
    Loop

    rs.Close
    Set rs = Nothing
    cn.Close
    Set cn = Nothing
   
    ContinueAlarmODBC
   
    Exit Sub
   
errHandl2:
     sError = CStr(Err.Number)
     Select Case Err.Number
     Case -2147217865
         MsgBox ("Ошибка номер:" + sError + " ошибка поиска таблицы в БД")
         cn.Close
         ContinueAlarmODBC
     Case -2147467259
    MsgBox ("Ошибка номер:" + sError + " база данных тревог перегружена")
          cn.Close
          ContinueAlarmODBC
    Case Else
        MsgBox ("Ошибка номер:" + sError)
        cn.Close
        ContinueAlarmODBC
    End Select
     
End Sub


Private Sub SortTablTimer_OnTimeOut(ByVal lTimerId As Long)
`таймер ежедневной сортировки таблицы FIXALARMS

 
    PauseAlarmODBC
    ProejStr = System.ProjectPath
    CurDate = Date
    Set cn = New ADODB.Connection
    With cn
        .ConnectionString = "Provider=microsoft.jet.OLEDB.4.0;Data Source=" + _
   ProejStr + "\Alm\MDB\RetroAlm.mdb"
        Rem s = .DefaultDatabase
        .Open
    End With
   
  On Error GoTo errHandl1
   
   
    Set rs = New ADODB.Recordset
    rs.Open "select * from  TsTu_Table", cn, adOpenForwardOnly, adLockReadOnly
    Do While Not rs.EOF
        ts_tag = rs("TS_TAGNAME") + " "
        sql = "INSERT INTO " + ts_tag + "SELECT * FROM FIXALARMS where ALM_
  TAGNAME like '" + ts_tag + "%'"   
        rs.MoveNext
    Loop
`делаем из таблицы FIXALARMS выборку тревог в которых поле ALM_TAGNAME равно значению `поля TS_TAGNAME таблицы TsTu_Table и вставляем эту выборку в таблицу с аналогичным `именем

`работа с полем TU_TAGNAME тут не описываю, т.к. у каждого индивидуальные особенности `телеупраления объектами
   
 sql = "delete from FIXALARMS where not ALM_DATELAST like '%" + CStr(CurDate) + "%'"
`удаляем отсортированные и устаревшие записи из таблицы FIXALARMS
 cn.Execute sql
 rs.Close
 Set rs = Nothing
 cn.Close
 Set cn = Nothing
   
  ContinueAlarmODBC
   
  Exit Sub
errHandl1:
     sError = CStr(Err.Number)
     Select Case Err.Number
     Case -2147217865
         MsgBox ("Ошибка номер:" + sError + " ошибка поиска таблицы в БД")
         cn.Close
         ContinueAlarmODBC
     Case -2147467259
    MsgBox ("Ошибка номер:" + sError + " база данных тревог перегружена")
          cn.Close
          ContinueAlarmODBC
    Case Else
        MsgBox ("Ошибка номер:" + sError)
        cn.Close
        ContinueAlarmODBC
    End Select
     
End Sub


Откомпилируем и проверим корректную работу написанных скриптов, подставляя нужное время в начало выполнения каждого.  После экспериментов с  таймером сортировки SortTablTimer в некотором случае в таблицах могут храниться записи с  одинаковыми значениями параметров, позднее мы избавимся мы от вывода дублирующих записей, применяя ключевое слово UNION  в SQL запросе.

Остается позаботиться о выводе данных из базы данных тревог в WorkSpace. Для решения данной задачи воспользуемся элементами VisiconX Data и VisiconX Grid.

Создадим отдельный рисунок «Retromanual.gfr» , в котором будут отображаться наши тревоги по выбранному объекту. Добавляем туда элемент VisiconX Data. В свойствах элемента на закладке «Провайдер» из списка выбираем «Microsoft OLE DB Provider for ODBC Drivers»:

Image 

На закладке «База данных» в списке имен DNS выбираем созданный ними ранее источник данных ODBC, и удостоверяемся что контрольное соединение устанавливается:

Image

Для того чтобы связать наше создаваемое окно с конкретным объектом из среды WorkSpace нам нужна какая-то глобальная переменная, которая бы хранила на данный момент имя источника данных объекта; просто говоря – имя тега дискретного сигнала, с которым работает пользователь в данный момент. В нашем проекте эту роль играет  строковая переменная StrRetro. В нее записывается строковое значение из источника данных выбранного объекта. Пример записи переменной StrRetro на примере обработки кнопки одной из форм, работающий с выбранным объектом:


Dim ActiveDocumentObject As Object

Dim SelObj As Object
Dim FillObj As Object
Dim CurrentObj As Object
Set ActiveDocumentObject = Application.ActiveDocument.Page
Set SelObj = ActiveDocumentObject.SelectedShapes
Set CurrentObj = SelObj.Item(1)

Private Sub RetroButton_Click()
Set FillObj = FindLocalObject(CurrentObj, "AnimForegroundColor")
‘"AnimForegroundColor" – имя анимации объекта
User.StrRetro.CurrentValue = Right(Left(FillObj.Source, Len(FillObj.Source) -5), Len(FillObj.Source) - 19)
`если например FillObj.Source = «Fix32.NodeFIX.DI_TAG_00.F_CV», то
`User.StrRetro.CurrentValue станет равным «DI_TAG_00»
`т.е. текущему значению переменной StrRetro присваиваем имя тега, на которое `ссылается источник данных в анимации выбранного объекта
OpenPicture "RetroManual.grf"
Unload Me
End Sub


В настройках анимации для VisiconX Data ставим ссылку на переменную SrtRetro:

Image

Добавляем элемент VisiconX Grid.  В настройках анимации для свойства ADORecords этого элемента в качестве источника указываем набор записей элемента VisiconX Data:

Image 

В рисунок можно добавить элемент OLE в виде календаря, для удобства сортировки тревог по выбранной дате. Скрипт  рисунка «Retromanual.gfr» :

Public CurrMonth
Dim s, sError As String
Dim ts_tag, CdDate, ProejStr As String
`декларируем некоторые глобальные переменные


Private Sub CFixPicture_Activated()

Application.ActiveWindow.Caption = "Окно ретроспективы"
Calendar1.Value = System.CurrentDate
ProejStr = System.ProjectPath
s = vxData2.QP1
ts_tag = vxData2.QP1 + " "

sql1 = "SELECT ALM_DATELAST as ДАТА, ALM_TIMELAST as ВРЕМЯ, ALM_DESCR as ОПИСАНИЕ, ALM_VALUE as ЗНАЧЕНИЕ, ALM_OPNAME as ОПЕРАТОР, ALM_NATIVETIMELAST FROM " + s + " ORDER BY 6 DESC;"
`готовим запрос на выборку всех записей из таблицы с именем выбранного объекта

sql2 = "SELECT ALM_DATELAST as ДАТА, ALM_TIMELAST as ВРЕМЯ, ALM_DESCR as ОПИСАНИЕ, ALM_VALUE as ЗНАЧЕНИЕ, ALM_OPNAME, ALM_NATIVETIMELAST FROM FIXALARMS WHERE ALM_TAGNAME LIKE '" + s + " %' "
`готовим запрос на выборку записей относящихся к объекту из таблицы FIXALARMS

vxData2.SQLCommand = sql2 + " UNION " + sql1
`объеденяем запросы и отсылаем vxData2 для выполнения команды SQL

Exit Sub

errHandl:
`обработаем возможные ошибки
     sError = CStr(Err.Number)
     Select Case Err.Number
     Case -2147217865
         MsgBox ("Ошибка номер:" + sError + " - по выбранному сигналу ретроспектива не ведется")
     Case -2147467259
        MsgBox ("Ошибка номер:" + sError + " – база данных тревог перегружена запросами, повторите попытку позже")
    Case Else
        MsgBox ("ошибка номер:" + sError)
    End Select
   
End Sub


В итоге кликая по контролируемому объекту в среде WorkSpace  и пожелавши ознакомиться с историей его тревог, перед нами открывается приблизительно такое вот окно:

Image 

Как видно, встроенные стандартные средства iFix позволяют пользователям, имеющим навыки основ программирования, довольно обширные возможности для организации систем диспетчерского контроля. И, по моему очень хорошо, что всего этого можно достичь, не тратясь на дополнительные программные средства.

Двигаясь далее в этом направлении можно на основе SQL запросов легко организовывать различные журналы и отчеты. Все зависит от Вашего желания и фантазии.

 

Заяц Денис Михайлович, Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
инженер службы средств диспетчерского и технологического управления РУП «МИНСКЭНЕРГО»

 
Какую SCADA предпочтете Вы?
 

Lamoda RU