Opc сервер своими руками

Opc сервер своими руками

OPC — интерфейс интеграции разнородных систем и устройств с различными протоколами обмена. В настоящее время механизм и спецификация OPC является основным инструментом для обмена данными в системах автоматицации и учета. OPC DA в этом отношении является уже несколько устаревшим, но пока еще самым распространенным стандартом. На смену ему уже несколько лет наступает новый, объединенный, мультиплатформенный стандарт OPC UA. Кроме этого существуют еще две менее распространенных спецификации OPC HDA (для запроса архивных данных, то есть когда один тег имеет еще одно измерение — время) и OPC AE (специфический стандарт для передачи тревог и событий). Я не писал серверов OPC HDA и AE, поэтому ничего полезного рассказать вам не смогу.

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

Вернемся к написанию простуйшего OPC DA сервера для типового устройства. Кстати это совсем необязательно, что сервер пишется для интеграции именно устройств. Нет, на другой стороне может быть все, что угодно от базы данных, до текстовых файлов или даже человека, который эти данные вводит через любой интерфейс. В общем случае нам нужно знать лишь аппаратный интерфейс взаимодействия с удаленным объектом и протокол обмена с ним.

Итак мы имеем:
некое устройство с последовательным интерфейсом
протокол обмена происходит в режиме запрос-ответ
мастером является устройство, клиент-сервер подключается и запрашивает данные

Первое, что нам понадобиться среда разработки. Я буду использовать самую распространенную Visual Studio 2008 (2005,2010 итп). Итак, среда разработки установлена, устройство подключено, с чего начать.

Разумнее всего начинать с ознакомления с протоколом обмена. Его необходимо распечатать, чтобы был всегда под рукой. Далее, если для устройства существует программное обеспечение (конфигураторы, программы сбора) — смело ставим их. Первая из утилит, которая нам понадобиться — монитор порта обмена. Я использую старенькую Portmon (1999 Mark Russinovich http://www.sysinternals.com). Не самая удачная программа, так как нехватает многих функций типа просмотра флагов всех регистров последовательного порта и бывает отъедает много памяти при долгой наработке, но меня она устраивает уже много лет.

Естественно для других интерфейсов в ход пойдут другие программы, например для ethernet снифферы. Для протокола modbus полезно сразу установить несколько утилит, которые позволят опрашивать все регистры, что бы проверить собственные посылки на корректность.

Так, как наше устройство работает по последовательному интерфейсу, запускаем монитор порта и открываем нужный порт. Запускаем сервисное программное обеспечение и последовательно проделываем все важные операции из числа возможных, такие как

  • запрос даты и времени устройства
  • запрос информации об устройстве
  • запрос текущих значений
  • запрос статуса устройства

После каждого действия необходимо копировать переданные и полученные посылки в отдельный документ с комментарием произведенного действия и очищать буфер в мониторе для дифференциации запросов.

Это будет необходимо если возникнут проблемы при отладке сервера и:

  • сравнения посылок, генерируемых собственным сервером и сервисным ПО
  • сравнения регистров обмена, таймаутов между посылками
  • чтобы удостовериться, что дело не в коде

Сервер буду писать на базе привычной для меня библиотеки lighopc (www.ipi.ac.ru/lab43/lopc-ru.html). Существет немало других библиотек и каркасов серверов и клиентов, которые можно найти в интернете, со многими я в сове время поработал, но остановился именно на lightopc из за его простоты. Этот проект давно уже не развивается и не поддерживает иные спецификации кроме OPC DA.

Теперь приступаем непосредственно к написанию сервера. Запускаем VS, выбираем тип проекта — консольное приложение, а сам проект оставляем пустым. Я использую консольное приложение, посколько в старое добрую консоль удобно выводить то, чем в данный момент занимается сервер, включая текущие значения параметров, запросы/ответы от устойств или его собственные ошибки.

Создаем исходный пустой файл сервера. Далее я не буду углубляться в проектирование и написание классов, распределение их по отдельным файлам и модулям, так как моя задача лишь показать саму суть, то есть создать максимально простой образец сервера. Итак, минимальный проект OPC сервера будет состоять из исходного файла на языке C, заголовочного файла, файла интерфейсов и нескольких библиотек.

Создаем и добавляем в проект файлы:
— device.h
— device.cpp

Необходимо скачать с сайта lighopc (www.ipi.ac.ru/lab43/lopc-ru.html) собранные библиотеки lightopc-0.888-0313bin. Внутри архива будут файлы lightopc.dll, lightopc.lib, их нужно добавить в проект.
Оттуда же скачиваем библиотеку для ведения лог-файлов сервера unilog-0.55-1227. Внутри будут файлы unilog.dll и unilog.lib, их также добавляем в проект.
Далее нам понадобиться библиотека opcda из SDK (opcfoundation.org), ее заголовочный суем туда же и линкуем из программы.
#include "opcda.h"
Программный модуль последовательного интерфейса можно использовать любой из множества представленных в инете или написать свой класс. Я буду использовать небольшой класс с соответствующим названием serialport и типовыми функциями открытия, закрытия порта, записи и чтения из порта. Исходный коды можно посмотреть в архиве, который прилагается к этой статье.

Читайте также:  Бесплатные программы для снятия видео с экрана

Это наиболее правильное решение, хотя еще гибче будет создать прослойку в виде модуля, который в зависимости от типа интерфейса будет использовать те или иные функции для работы с ним, а внешние вызовы из основной программы остануться одинаковыми. Это позволит иметь один исходный код и легко переключаться между типами интерфейсов, или даже совмещать при работе одного сервера.
Если будет использоваться конвертер интерфейсов (RS-CAN, RS-Ethernet), то к проекту потребуется подключить соответствующую библиотеку с API.

Для работы серверу требуется задать уникальный идентификатор GUID, который можно сгенерировать с помощью специальной стандартной утилитки guidgen.exe, которая входит в состав Visual Studio (Microsoft Visual Studio 9.0Common7Tools). Выбираем 2. DEFINE_GUID -> Copy и вставляем результат из буфера в исходный файл под другими определениями.

// <76B8B688-38B1-4802-9C21-1E34A3097778>
DEFINE_GUID(GID_UnknownDeviceOPCserverExe,
0x76b8b688, 0x38b1, 0x4802, 0x9c, 0x21, 0x1e, 0x34, 0xa3, 0x9, 0x77, 0x78);

Что точно потребуется определить.
#define _WIN32_DCOM // Разрешает расширения DCOM
#define INITGUID // Инициализирует OLE константы
#define ECL_SID "opc.device" // идентификатор OPC сервера

Точно потребуются следующие библиотеки.
#include // стандартный ввод-вывод
#include // математические функции
#include "server.h" // заголовочный файл этого сервера
#include "unilog.h" // библиотека для лог-файлов
#include "opcda.h" // базовые функции OPC:DA
#include "lightopc.h" // заголовлочный файл light OPC

Определяем переменные для lightopc.
static const loVendorInfo vendor = <0,1,8,0,"Unknown device OPC Server" >; // версия сервера (Major/Minor/Build/Reserv)
static int OPCstatus=OPC_STATUS_RUNNING; // статус OPC сервера
loService *my_service; // экземпляр lightOPC сервера

Память под переменные — теги сервера можно выделять динамически, а можно взять с запасом статически, TAGS_NUM_MAX — максисмальное количество тегов.
static CHAR *tn[TAGS_NUM_MAX]; // Названия тегов
static loTagValue tv[TAGS_NUM_MAX]; // Значения тегов
static loTagId ti[TAGS_NUM_MAX]; // Идентификаторы тегов

#define LOGID logg,0 // идентификатор лог-файла
#define LOG_FNAME "ecl.log" // имя лог-файла
unilog *logg=NULL; // указатель на создаваемый файл журнала
Еще немного об основах. Правилом хорошего тона является предоставление пользователю удобного механизма для редактирования свойств сервера (параметров обмена, механизма формирования тегов, итп). Можно написать отдельное приложение для формирования конфигурационного файла сервера, которое может совмещать в себе функции также и OPC клиента, отображающего данные. Это вполне обособленная и достаточно тривиальная задача, которую я не буду разбирать. В любом случае конфигуратор на выходе будет иметь файл с настройками сервера и очень важно, чтобы этот файл был правильно структурирован, читабелен и была возможность его ручного редактирования. Оптимальным выбором будет использование формата xml для хранения данных. В этом случае отлично подойдет бесплатная библиотека tinyxml для парсинга xml-файлов (http://sourceforge.net/projects/tinyxml/).

#include "tinyxml/tinyxml.h" // XML parser lib

Базовый класс myClassFactory: public IClassFactory и его типвые функции я обычно выделяю в отдельный файлик, чтобы он не мозолил глаза. Он неизменен и его код вам на первых порах разбирать не потребуется. Просто скачиваем и вставляем перед основным кодом.

Все возможные входы в программу ведут к нашей функции mymain.

Далее разбираем содержимое функции main, прямо по пунктам.
INT mymain(HINSTANCE hInstance, INT argc, CHAR *argv[]);

1) Сходу создаем лог-файл, чтобы записывать туда все действия сервера. На отладку у каждого из нас есть свои взгляды. Я, например, люблю писать все действия программы и все промежуточные значения переменных в логи, чтобы потом апализировать поведение программы шаг за шагом и только уже если возникает какая-то коллизия, тогда делаю пошаговую отладку в встроенном дебаггере.

Итак, создаю файл, здесь LOG_FNAME — имя файла.
logg = unilog_Create(ECL_SID, LOG_FNAME, NULL, 0, ll_DEBUG); // level [ll_FATAL. ll_DEBUG]
UL_INFO((LOGID, "Unknown device OPC server start"));>

2) Что должен делать наш сервер будучи запущеным? Прежде всего работать, а работает сервер в связке с OPC-клиентом (нет, он конечно может молотить и вхолостую, но без подключенных клиентов это не всегда будет иметь смысл). Хотя я оставлю этот момент на ваше усмотрение. В конце концов никто не запрещает вам написать сервер, который будет работать всегда и даже без подключенных клиентов и заниматься чем то не менее полезным, чем предоставление данных. Например, я интегрировал в сервер функцию записи данных в текстовые файлы и базу данных, а клиентам передавал эти данные по мере их подключения. Это не совсем корректно с точки зрения иделолгии, но весьма практично.
Для того, чтобы клиенты могли подключиться к серверу им необходимо знать есть ли нужный сервер в списке зарегистрированных серверов на локальной или удаленной машине. Для этого нашему устройству необходимо зерегистрироваться в системе.
За это отвечает функция loServerRegister(&GID_ECLOPCserverExe, eProgID, eClsidName, argv0, 0)
GID_ECLOPCserverExe — сгенерированный ранее UID
eProg >argv0 — командная строка (собственно этот путь и зарегистрирует операционная система в реестре)
Запуск этой функции предлагаю осуществлять традиционным способом — путем передачи ключа в командной строке.
Например, ключ /r или /register, будет давать понять серверу, что требуется зарегистрировать сервер в системе.
Ключ /u или /unregister, будет давать понять серверу, что требуется удалить сервер из системы.

Читайте также:  Hiper power bank rp8500

За удаление сервера из системы отвечает функция loServerUnregister(&GID_ECLOPCserverExe, eClsidName).
Не забываем удалить за собой созданный экземпляр лога
unilog_Delete(logg); logg = NULL;
В иных случаях сервер должен работать в штатном режиме.

3) Перед началом работы инициализируем ключевую библиотеку COM объектов функцией CoInitializeEx(NULL, COINIT_MULTITHREADED).

4) Дальше опять же существует два конкретных варианта развития событий. Мы либо пытаемся инициализировать интерфейс, попытаться найти устройство и в случае успеха двигаться дальше, а в случае неудачи закончить работу сервера. Либо мы в любом случае регистрируем экземпляр сервера, не дожидаясь готовности интерфейса и ответа от устройства и работаем вхолостую, ожидая его подключения. В нашем случае не сильно принципиально, поэтому для начала запускаю функцию инициализации интерфейса InitDriver(), описание которой приведу позже. Если инициализация проваливается, не забываю кроме удаления экзепляра модуля логов ище и провести деинициализацию COM CoUninitialize(). Также удаляем и сам loService — loServiceDestroy(my_service).

5) Создание и заполнение структуры драйвера опроса логически выделено в функцию InitDriver(), вместе с инициализацией интерфейса.
Производить парсинг конфигурационного файла, открывать и настраивать последовательный порт я не буду. В конце концов мы же не с реальным устройством будем работать и загромождать программу пока не буду. Если потребуется, то опишу этот раздел позже.

6) Инициализируем теги. Создам два фиктивных устройства по 3 тега на каждый. Первый тег будет целочисленным, а остальные два с плавающей запятой.
Здесь tag_add — счетчик добавляемых тегов.

7) Переходим к инициализации сервера в системе, чтобы другие процессы-клиенты смогли подключиться к нашему серверу. CoRegisterClassObject(GID_UnknownDeviceOPCserverExe, &my_CF, CLSCTX_LOCAL_SERVER|CLSCTX_REMOTE_SERVER|CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &objid)
my_CF — экземпляр класса myClassFactory, основного класса сервера, который мы определили выше (у меня он выделен в отдельный файл opc_main.h).
Флаги CLSCTX описаны здесь (http://msdn.microsoft.com/en-us/library/windows/desktop/ms693716%28v=vs.85%29.aspx), я выставил возможность работы как локально, так и удаленно, если клиент будет подключаться по сети.

8) При подключении нового клиента счетчик увеличивается на единицу, что и делает функция класса myClassFactory -> AddRef (), my_CF.Release() — наоборот служит обратной цели и уничтожает одного клиента, уменьшая счетчик на единицу. Пока количество, подключенных клиентов, больше нуля — функция in_use возвращает еденицу.

9) Все. Теперь мы можем полноценно работать. Например, опрашивать устройство в цикле.
while(my_CF.in_use()) if (WorkEnable) poll_device(); То есть пока хотя бы один клиент подключен к серверу и собственный флаг сервера установлен мы в цикле производим опрос устройства. Собственный флаг WorkEnable я ввожу, чтобы иметь возможность в любой момент прекратить работу сервера не дожидаясь отключения всех клиентов.

10) Функция опроса данных poll_device() — основная функция сервера, в которой объеденен опрос устройства и обновление значений тегов. Если вы будете использовать несколько экземпляров одного интерфейса (да даже если и не планируете делать это сейчас), то потребуется выделить каждому отдельный поток, иначе формирование/обработка запросов/ответов будет производиться последовательно в одном, что крайне негативно скажется на скорости работы всего сервера. Проще говоря каждому COM-порту, каждому сокету и каждому клиенту мы выделяем отдельный поток, который будет заниматься обменом данными по этому интерфейсу.
Вот, пример организации такой работы:

Мы вообще ничего читать не собираемся, поэтому значения будем формировать случайным образом, с помощью генератора случайных чисел.

11) Запуск сервера. Для запуска сервера потребуется любой OPC клиент. Я уже давно использую удобный и бесплатный Matrikon OPC Explorer (http://www.matrikonopc.com/products/opc-desktop-tools/opc-explorer.aspx). Но еще до запуска сервера мы должны проделать ряд операций.
Во-первых не забываем переписать библиотеки, которые использует наша программа (unilog.dll, lighopc.dll) в директорию с исполняемым файлом.
Во-вторых необходимо зарегистрировать сервер с системе, для чего запустить его с ключом /r (fistopc.exe /r). Если все пройдет гладко то появится окно, уведомляющее об успешном завершении операции и в рабочей или системной директории (%SYSTEMROOT%SYSTEM32) появится лог-файл (device.log).

Запускаем OPC клиент. В левой части показывается дерево зарегистрированных в системе OPC серверов. Выбираем из них наш сервер opc.device и подключаемся к нему. Если соединение происходит, создаем группу тегов и добавляем все теги в режим просмотра. Должно получиться чтото вроде этого.

Пример лог-файла правильно работающего сервера. Скачать проект

Задача стоит так, что надо написать OPC сервер, который будет в дальнейшем работать из одного .exeшника с остальной программой. Первоначально было опробовано решение с использованием библиотек и примеров GreyBox OPC, но программа получилась нестабильной, то работала, то нет по абсолютно непонятной причине. Скорее всего, во всем виновата прямота рук.

Поэтому прошу посоветовать какую-либо документацию или статьи по теме, или если у кого есть исходники с комментариями.

Данный топик просвещен проблеме обмена данными в системах промышленной автоматизации му ПЛК и различным программном обеспечении. Прежде чем приступить непосредственному к изложению, хочу сказать, что нахожусь в дурацком положении… Дело в том, что основная часть моих коллег по цеху не являются ИТ-специалистами и работаю в рамках тех инструментальных средств, которые являются стандартом «де-факто» — SCADA пакеты, среды разработки для ПЛК и OPC сервера. Мало кого из них интересует, что находится под «капотом» этих инструментов, хотя большинство проблем, об которые они спотыкаются, кроются именно там и заложены в базовых технологиях. С другой стороны АСУ ТП довольно специфичная область и я не уверен, что программист без опыта работы в данной сфере сможет проникнутся тем, что я попытаюсь донести в этом посте. Вот и получается, что данный топик предназначен для небольшого процента специалистов, которые разбираются в ИТ и АСУ ТП одновременно.

Читайте также:  Gp powerbank quick 3 мигает красный индикатор

Критика OPC

С развитием АСУ ТП перед производителями ПЛК и программного обеспечения встала проблема взаимодействия му устройствами и ПО работающими по разным протоколам. Решением этой проблемы по инициативе Microsoft стал протокол OPC в основе которого первоначально лежала DCOM технология. Данный протокол на текущий момент используется повсеместно и не смотря на довольно развитую номенклатуру спецификаций, подавляющее большинство реализаций основывается на DCOM технологии, что и стало причиной множества проблем:

  • Работа в сети. OPC имеет клиент северную архитектуру на базе DCOM технологии компании Microsoft. Поддержка обмена данными по сети в DCOM ограничена и требует дополнительной настройки безопасности узлов. Таким образом внедрение OPC в многоуровневых корпоративных сетях Intranet затруднено, а передача данных через Internet просто невозможна. Данный недостаток критичен при построении систем уровня MES или ERP, что приводит к необходимости внедрения специальных шлюзов, которые транслируют данные между собой в своём формате не ограниченным DCOM и предоставляют данные по OPC.
  • Привязка к Windows. DCOM технология поддерживает только ОС Windows, что не позволяет разворачивать OPC сервера в контроллерной части и создавать клиентское ПО АСУТП для мобильных устройств (на базе iOS, Android и т. д.). По той же причине нет возможности использовать устойчивые к вирусным атакам системы на базе Unix(Linux) для сбора и хранения данных.
  • Конфигурация. Основным понятием OPC технологии является тег, чтобы получить данные о каком либо сигнале необходимо «завести» его в конфигурации OPC сервера и в конфигурации каждого клиентского ПО. Таким образом затраты на конфигурацию возрастают пропорционально количеству клиентов и уровней в системе автоматизации. Кроме того, часто эти уровни или клиенты обслуживаются разными организациями, которые просто могут не узнать оперативно о новых данных, что приводит к неактуальности показаний их систем.
  • Закрытость протокола. Технология OPC позиционируется как «открытая» технология, но это совсем не так. Доступ к спецификации и к инструментам для разработки предоставляется только членам OPC Fundation на платной основе. Такая бизнес модель сплотила между собой крупные компании производители, а все остальные стали потребителями уже готовых продуктов. Даже для тривиальной задачи в области АСУ ТП на базе OPC приходится что-то покупать.

Как видите проблем достаточно много, чтобы начать поиск альтернативы.

REST в промышленной автоматизации

Когда я в качестве хобби занимался разработкой веб приложений на RubyOnRails, я был поражен как просто решаются задачи передачи данных с помощью REST модели! Тогда я задумался о возможности применения такого подхода в АСУ ТП. Как позже оказалась, что данная идея уже сформулирована специалистом из Австралии Томом Тoденхэмом, здесь можно прочитать его труд по этой теме (или мой перевод). Чтобы пробудить интерес к этой идеи, приведу тезисы о ее преимуществах:

  • Работа в сети. Использование HTTP протокол, как транспорт, позволяет передавать данные через интернет и многоуровневые корпоративные сети. Так же он не требует дополнительной настройки узлов в отличие от OPC технологии.
  • Независимость от платформы. HTTP поддерживаться всеми операционными системами, что позволяет создавать клиенты под мобильные устройства. К тому же, ввиду простоты HTTP возможно реализовывать REST сервера на уровне контроллеров и снимать с них данные без промежуточных шлюзов.
  • Конфигурация. Так как основным понятием REST является ресурс, то возможен групповой доступ к данным (подобно доступу к таблице в SQL), таким образом, новые данные в системе могут обрабатываться автоматически без дополнительной конфигурации. Так же стоит отметить, что HTTP позволяет не только получать данные с ресурсов, но и создавать и настраивать их, что позволяет управлять REST сервером со стороны клиентской части с помощью универсальных методов.
  • Открытость. REST использует открытые стандарты передачи и представления данных (HTTP, HTML, XML, JSON …), которые поддерживаются всеми языками программирования и платформами, что позволяет создавать приложения автоматизации с минимальными вложениями в инструментальные средства.

Как пример простой реализации на Ruby, можете почитать мою статью. Так же есть проект для REST доступа к периферии Arduino — RESTdunio

Ссылка на основную публикацию
Adblock detector