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





