Сервера 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».