понедельник, 5 сентября 2011 г.

Automation-интерфейс OPC

Сервера OPC в соответствие со спецификацией OPC DA 3.0 предоставляют два типа интерфейсов, так называемый automation-интерфейc и custom-интерфейс. На рис. 1 представлена архитектура OPC взаимодействия.

Рис. 1. Архитектура OPC взаимодействия.

Данная архитектура взаимодействия позволяет осуществлять пользовательским (клиентским) приложениям по средствам вышеназванных интерфейсов осуществлять обмен с OPC-сервером, который в свою очередь ведет обмен с физическим устройством (контроллером, измерительными устройствами и т.д.). В зависимости от настроек OPC-сервера, клиенты могут осуществлять как чтение, так и запись переменных.

В данной заметке будет кратко рассмотрен обмен с OPC-сервером через automation-интерфейс.

Клиентское приложение, используя automation-интерфейс, осуществляет обмен по custom-интерфейсу через библиотеку посредника (wrapper), как представлено на рис. 1. В данной заметке в качестве DLL библиотеки automation-интерфейса будет использован библиотека OPCDAAuto.dll.

Первым делом не плохо бы узнать, какие OPC-сервера зарегистрированы на том или ином узле. Для этого можно воспользоваться методом GetOPCServers класса OPCServer, ниже представлен фрагмент кода, который выведет список имен зарегистрированных на хосте OPC-серверов:

OPCServer opc_server = new OPCServerClass();

// запрашиваем список имен зарегестрированных OPC-серверов на локальном узоле.
object list_servers = opc_server.GetOPCServers("127.0.0.1");

// выводим на консоль список имен зарегестрированных OPC-серверов.
System.Array y = ((System.Array)(list_servers));
for (int i = 1; i <= y.Length; i++) {
    System.Console.WriteLine(y.GetValue(i).ToString());
}
Чтобы получить дополнительные сведения об OPC-сервере, можно воспользоваться методами класса OPCServer:
// подключаемся к серверу с именем serv_name (например, Mitsubishi.MXOPC.4) на локальном хосте
serv.Connect(serv_name, "localhost");

// выводим имя OPC-сервера
Console.WriteLine("Name: " + serv.ServerName);

// информацию о производителе
Console.WriteLine("Vendor: " + serv.VendorInfo);

// версия OPC-сервера
Console.WriteLine("Ver: " + serv.MajorVersion.ToString() + "." + serv.MinorVersion.ToString());

// состояние OPC-сервера
int state = serv.ServerState;
string msg = "Unknown";
switch((OPCServerState) state) {
    case OPCServerState.OPCRunning:
        msg = "Running"; break;
    case OPCServerState.OPCSuspended:
        msg = "Suspended"; break;
    case OPCServerState.OPCNoconfig:
        msg = "Has no configuration"; break;
    case OPCServerState.OPCFailed:
        msg = "Fail"; break;
    case OPCServerState.OPCTest:
        msg = "Test mode"; break;
    case OPCServerState.OPCDisconnected:
        msg = "Disconnected"; break;
    default: break;
}

Console.WriteLine("State: " + msg);

// время запуска OPC сервера
Console.WriteLine("Started: " + serv.StartTime.ToString("dd.MM.yyyy hh:mm"));
Подключившись к серверу, мы имеем также возможность получить сведения о его переменных и узлах, хранящих переменные (устройства или группы). На рис. 2 представлен пример древовидной организации хранения переменных в OPC-сервере.

Рис. 2. Древовидная организация хранения переменных в OPC-сервере. 

Вывести все имена переменных сервера можно двумя способами: первый способ, наиболее простой, вывести все имена с полным путем. Второй способ, самостоятельно обойдя дерево (может потребоваться, если вы, к примеру, собираетесь заполнить древовидную структуру, например, тот же графический элемент TreeView). Ниже представлен пример того, как вывести все имена с полным путем:
// подключаемся по имени к OPC-серверу на локальном хосте.
opc_server.Connect(serv_name, "localhost");

// создаем экземпляр браузера.
OPCBrowser browser = opc_server.CreateBrowser(); 

// переход к корню дерева переменных OPC-сервера
browser.MoveToRoot();

// получить имена всех переменных, включая те, что расположены
// в нижележащих узлах (аргумент true).
browser.ShowLeafs(true);
    
// выводим список имен переменных OPC-сервера
// важно: счет начинается с единицы!
for (int i = 1; i < browser.Count + 1; i++) {
    Console.WriteLine(browser.Item(i));
}
На рис. 3 представлен результат исполнения представленного выше фрагмента кода.

Рис. 3. Список имен переменных OPC-сервера с полным указанием к ним пути. 

При реализации второго способа (самостоятельный обход дерева), после вызова метода браузера для перехода к корню дерева вызываем рекурсивную функцию:
// вызываем рекурсивный метод печати дерева переменных
PrintBranch(browser, 0);

… … …

// Рекурсивная функция, которая обходит дерево с уровня lvl
public static void PrintBranch(OPCBrowser browser, int level) {
    string spaces = "";  // для визуальн. отлич. потомков от родител.
    for (int i = 0; i < level; i++) { spaces += "\t"; }

    // получить имена переменных только этого уровня (не вложенных)
    browser.ShowLeafs(false);
    for (int i = 1; i < browser.Count + 1; i++) {
        Console.WriteLine(spaces + browser.Item(i));
    }

    // получить имена узлов этого уровня
    browser.ShowBranches();
    
    for (int i = 1; i < browser.Count + 1; i++) {
        Console.WriteLine(spaces + ":: " + browser.Item(i));

        // опускаемся для обхода нижележащего уровня
        browser.MoveDown(browser.Item(i));
        PrintBranch(browser, level + 1);

        // возвращаемся на прежний уровень
        browser.MoveUp();

        // и вновь запрашиваем все узлы на этом уровне
        browser.ShowBranches();
    }
}
Разобравшись с тем, как выводить информацию об OPC-сервере и его содержимом, имеет смысл перейти к рассмотрению к тому, как осуществляется чтение и запись переменных OPC-сервера. 

Чтение с OPC-сервера можно осуществлять синхронно и асинхронно, при этом при синхронном чтении можно выбрать устройство чтения, либо кэш OPC-сервера, либо само устройство. При асинхронном чтении по умолчанию выполняется опрос с устройства. Важно отметить, что при чтении из кэша, убедитесь, что группа и переменная в активном состоянии, в противном случае, при чтении качество переменных будет OPC_QUALITY_OUT_OF_SERVICE, иными словами, какое реально сейчас значение переменной или группы переменных не известно. 

Чтобы выполнить чтение переменных, необходимо создать группу опрашиваемых переменных на сервере, занести в нее представляющие интерес переменные, после чего осуществить чтение данных:
// подключаемся к OPC-серверу на локальном хосте
OPCServer opc_server = new OPCServerClass();
opc_server.Connect(serv_name, "127.0.0.1");

// создаем экземпляр браузера
OPCBrowser browser = opc_server.CreateBrowser();
browser.ShowLeafs(true);

// создаем группу в OPC-сервере
OPCGroups groups = opc_server.OPCGroups;
groups.DefaultGroupIsActive = true;
OPCGroup group = groups.Add("MyGroup");

// указываем с какими переменными будем работать
OPCItems items = group.OPCItems;
int [] hserv = new int[browser.Count + 1];
int idtrans = 2000;

// добавляем переменные в группу и сохр. их идентифик.
// помните! индексы все с единицы!
for (int i = 1; i < browser.Count + 1; i++) {
    hserv[i] = items.AddItem(browser.Item(i), idtrans).ServerHandle;
}

Array handlers = (Array) hserv;
Array errors;   // массив отображающий статус считан. перемен.
Array values;   // массив значений переменных
object qualities;   // массив качества переменных
object timestamps;  // массив временных штампом

// синхронно считываем с устройства все значения переменных
group.SyncRead((short) OPCDataSource.OPCDevice, browser.Count, ref handlers, out values, out errors, out qualities, out timestamps); 

// выводим на печать результаты (индексы с единицы!)
for (int i = 1; i < browser.Count + 1; i++) {
    Console.WriteLine(browser.Item(i) + ":");
    Console.WriteLine("\tValue: " + values.GetValue(i).ToString());
    
    // выясняем качество переменной
    switch(int.Parse(((Array) qualities).GetValue(i).ToString())) {
        case (int) OPCQuality.OPCQualityGood:
            Console.WriteLine("\tQuality: Good"); break;
        default:
            Console.WriteLine("\tQuality: Bad"); break;
    }
    Console.WriteLine("\tTimeStamp: " + ((DateTime)((Array) timestamps).GetValue(i)).ToString("dd.MM.yyyy hh:mm:ss"));
    Console.WriteLine();
}

opc_server.Disconnect(); // разрываем соединение
На рис. 4 представлен результат деятельности представленного фрагмента программы, выполняющей чтение значений переменных OPC-сервера.

Рис. 4. Результат чтения значений переменных OPC-сервера. 

В заключение данной заметки, в качестве демонстрации работы с методами записи данных, осуществим запись строки «HELLO SERVER» в строковую переменную Dev01.StringVar:
// подключаемся к OPC-серверу
OPCServer serv = new OPCServerClass();
serv.Connect(serv_name, "127.0.0.1");

// создаем группу и добавляем в нее переменную
// счет идет с единицы!
OPCGroup group = serv.OPCGroups.Add("MyGroup");
int [] hserv = new int[2];
hserv[1] = group.OPCItems.AddItem("Dev01.StringVar", 2000).ServerHandle;

// Записываем значение переменной в OPC-сервер
object [] values = new object[2]; // одно значение будем писать
values[1] = "HELLO SERVER";

Array hs = (Array) hserv;
Array vals = (Array) values;
group.SyncWrite(1, ref hs, ref vals, out err);

Thread.Sleep(500); // даем серверу время на исполнение

// Выводим переменную сервера после изменений
group.SyncRead((short) OPCDataSource.OPCDevice, 1, ref hs, out outval, out err, out qualities, out stamps);
Console.WriteLine(outval.GetValue(1).ToString());

Таким образом, используя automation-интерфейс, можно создавать полноценные клиенты к OPC-серверам. В данной заметке были рассмотрены далеко не все возможности (сервисы), предоставляемые automation-интерфейсом, более полно ознакомиться с automation-интерфейсом можно непосредственно в соответствующей спецификации «OPC Data Access Automation Specification».

1 комментарий:

  1. Здравствуйте

    Возникло несколько вопросов касательно синхронного чтения данных с орс-сервера
    1. Возможно ли использование вышеприведенного кода для opc da auto 2.02? В части работы с орс-браузером Ваш код(без изменений) работает, но в части синхронного чтения(с небольшими изменениями) выдает ошибку "Попытка чтения или записи в защищенную память". В Ваш код мной были внесены следующие изменения:

    Array handlers = (Array) hserv;
    Array errors; // массив отображающий статус считан. перемен.
    Array values; // массив значений переменных

    заменил на:

    int[] handler = hserv;
    int[] errors;
    object[] values;

    Данные изменения были вызваны требованием на тип параметров метода синхронного чтения библиотеки орс стороннего производителя.
    Подключение произвожу к серверу того же производителя. Никакой документации на библиотеку не найдено, спецификация орс так же не внесла ясности.
    Использую VS2010, win7 x64

    ps. Поясните, пожалуйста, каков физический смысл int idtrans = 2000;

    благодарю за помощь.

    ОтветитьУдалить